From 582158f70e6c63980cce17d408a0cc435a0d985f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 30 Mar 2018 14:09:51 +0100 Subject: [PATCH] Added tags to chapters and books Closes #121 --- app/Http/Controllers/ChapterController.php | 9 ++-- app/Repos/EntityRepo.php | 26 ++++++---- resources/assets/js/vues/tag-manager.js | 8 ++-- resources/assets/sass/_blocks.scss | 3 ++ resources/assets/sass/_components.scss | 4 ++ resources/assets/sass/_forms.scss | 3 ++ resources/lang/en/entities.php | 4 +- resources/views/books/form.blade.php | 41 +++++++++------- resources/views/books/show.blade.php | 23 ++++++--- resources/views/chapters/form.blade.php | 9 ++++ resources/views/chapters/show.blade.php | 9 ++++ resources/views/components/tag-list.blade.php | 10 ++++ .../views/components/tag-manager.blade.php | 23 +++++++++ resources/views/pages/form-toolbox.blade.php | 25 ++-------- resources/views/pages/show.blade.php | 11 +---- tests/Entity/TagTest.php | 47 +++++++++++++++---- 16 files changed, 173 insertions(+), 82 deletions(-) create mode 100644 resources/views/components/tag-list.blade.php create mode 100644 resources/views/components/tag-manager.blade.php diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index a4e0b6409..b737afc6d 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -107,17 +107,14 @@ class ChapterController extends Controller * @param $bookSlug * @param $chapterSlug * @return Response + * @throws \BookStack\Exceptions\NotFoundException */ public function update(Request $request, $bookSlug, $chapterSlug) { $chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug); $this->checkOwnablePermission('chapter-update', $chapter); - if ($chapter->name !== $request->get('name')) { - $chapter->slug = $this->entityRepo->findSuitableSlug('chapter', $request->get('name'), $chapter->id, $chapter->book->id); - } - $chapter->fill($request->all()); - $chapter->updated_by = user()->id; - $chapter->save(); + + $this->entityRepo->updateFromInput('chapter', $chapter, $request->all()); Activity::add($chapter, 'chapter_update', $chapter->book->id); return redirect($chapter->getUrl()); } diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php index ece9aa305..e94d34369 100644 --- a/app/Repos/EntityRepo.php +++ b/app/Repos/EntityRepo.php @@ -492,14 +492,19 @@ class EntityRepo public function createFromInput($type, $input = [], $book = false) { $isChapter = strtolower($type) === 'chapter'; - $entity = $this->getEntity($type)->newInstance($input); - $entity->slug = $this->findSuitableSlug($type, $entity->name, false, $isChapter ? $book->id : false); - $entity->created_by = user()->id; - $entity->updated_by = user()->id; - $isChapter ? $book->chapters()->save($entity) : $entity->save(); - $this->permissionService->buildJointPermissionsForEntity($entity); - $this->searchService->indexEntity($entity); - return $entity; + $entityModel = $this->getEntity($type)->newInstance($input); + $entityModel->slug = $this->findSuitableSlug($type, $entityModel->name, false, $isChapter ? $book->id : false); + $entityModel->created_by = user()->id; + $entityModel->updated_by = user()->id; + $isChapter ? $book->chapters()->save($entityModel) : $entityModel->save(); + + if (isset($input['tags'])) { + $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']); + } + + $this->permissionService->buildJointPermissionsForEntity($entityModel); + $this->searchService->indexEntity($entityModel); + return $entityModel; } /** @@ -518,6 +523,11 @@ class EntityRepo $entityModel->fill($input); $entityModel->updated_by = user()->id; $entityModel->save(); + + if (isset($input['tags'])) { + $this->tagRepo->saveTagsToEntity($entityModel, $input['tags']); + } + $this->permissionService->buildJointPermissionsForEntity($entityModel); $this->searchService->indexEntity($entityModel); return $entityModel; diff --git a/resources/assets/js/vues/tag-manager.js b/resources/assets/js/vues/tag-manager.js index 97c00487e..177af681f 100644 --- a/resources/assets/js/vues/tag-manager.js +++ b/resources/assets/js/vues/tag-manager.js @@ -2,7 +2,8 @@ const draggable = require('vuedraggable'); const autosuggest = require('./components/autosuggest'); let data = { - pageId: false, + entityId: false, + entityType: null, tags: [], }; @@ -48,9 +49,10 @@ let methods = { }; function mounted() { - this.pageId = Number(this.$el.getAttribute('page-id')); + this.entityId = Number(this.$el.getAttribute('entity-id')); + this.entityType = this.$el.getAttribute('entity-type'); - let url = window.baseUrl(`/ajax/tags/get/page/${this.pageId}`); + let url = window.baseUrl(`/ajax/tags/get/${this.entityType}/${this.entityId}`); this.$http.get(url).then(response => { let tags = response.data; for (let i = 0, len = tags.length; i < len; i++) { diff --git a/resources/assets/sass/_blocks.scss b/resources/assets/sass/_blocks.scss index 4cf2397bc..f876ff281 100644 --- a/resources/assets/sass/_blocks.scss +++ b/resources/assets/sass/_blocks.scss @@ -226,6 +226,7 @@ text-align: center; justify-content: center; width: 28px; + flex-grow: 0; padding-left: $-xs; padding-right: $-xs; &:hover { @@ -237,6 +238,7 @@ } > div .outline input { margin: $-s 0; + width: 100%; } > div.padded { padding: $-s 0 !important; @@ -251,6 +253,7 @@ > div { padding: 0 $-s; max-width: 80%; + flex: 1; } } diff --git a/resources/assets/sass/_components.scss b/resources/assets/sass/_components.scss index 84eebc89b..31e006e27 100644 --- a/resources/assets/sass/_components.scss +++ b/resources/assets/sass/_components.scss @@ -604,3 +604,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { color: #999; } } + +#tag-manager .drag-card { + max-width: 500px; +} \ No newline at end of file diff --git a/resources/assets/sass/_forms.scss b/resources/assets/sass/_forms.scss index 11adc7951..3ab2de522 100644 --- a/resources/assets/sass/_forms.scss +++ b/resources/assets/sass/_forms.scss @@ -237,6 +237,9 @@ input:checked + .toggle-switch { &.open .collapse-title label:before { transform: rotate(90deg); } + &+.form-group[collapsible] { + margin-top: -($-s + 1); + } } .inline-input-style { diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index 8a47ae011..c25dbb623 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -200,8 +200,10 @@ return [ * Editor sidebar */ 'page_tags' => 'Page Tags', + 'chapter_tags' => 'Chapter Tags', + 'book_tags' => 'Book Tags', 'tag' => 'Tag', - 'tags' => '', + 'tags' => 'Tags', 'tag_value' => 'Tag Value (Optional)', 'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.", 'tags_add' => 'Add another tag', diff --git a/resources/views/books/form.blade.php b/resources/views/books/form.blade.php index 0620ae976..880149777 100644 --- a/resources/views/books/form.blade.php +++ b/resources/views/books/form.blade.php @@ -11,23 +11,32 @@
-
- -
-
-

{{ trans('common.cover_image_description') }}

+
+ +
+
+

{{ trans('common.cover_image_description') }}

- @include('components.image-picker', [ - 'resizeHeight' => '512', - 'resizeWidth' => '512', - 'showRemove' => false, - 'defaultImage' => baseUrl('/book_default_cover.png'), - 'currentImage' => @isset($model) ? $model->getBookCover() : baseUrl('/book_default_cover.png') , - 'currentId' => @isset($model) ? $model->image_id : 0, - 'name' => 'image_id', - 'imageClass' => 'cover' - ]) -
+ @include('components.image-picker', [ + 'resizeHeight' => '512', + 'resizeWidth' => '512', + 'showRemove' => false, + 'defaultImage' => baseUrl('/book_default_cover.png'), + 'currentImage' => @isset($model) ? $model->getBookCover() : baseUrl('/book_default_cover.png') , + 'currentId' => @isset($model) ? $model->image_id : 0, + 'name' => 'image_id', + 'imageClass' => 'cover' + ]) +
+
+ +
+
+ +
+
+ @include('components.tag-manager', ['entity' => isset($book)?$book:null, 'entityType' => 'chapter']) +
diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index d3a51cb3a..9f021b2b0 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -68,19 +68,28 @@
@endif - @if(count($activity) > 0) -
-

@icon('time') {{ trans('entities.recent_activity') }}

- @include('partials/activity-list', ['activity' => $activity]) -
- @endif -

@icon('info') {{ trans('common.details') }}

@include('partials.entity-meta', ['entity' => $book])
+ + @if($book->tags->count() > 0) +
+

@icon('tag') {{ trans('entities.book_tags') }}

+
+ @include('components.tag-list', ['entity' => $book]) +
+
+ @endif + + @if(count($activity) > 0) +
+

@icon('time') {{ trans('entities.recent_activity') }}

+ @include('partials/activity-list', ['activity' => $activity]) +
+ @endif @stop @section('container-attrs') diff --git a/resources/views/chapters/form.blade.php b/resources/views/chapters/form.blade.php index 19cf65a61..fde460844 100644 --- a/resources/views/chapters/form.blade.php +++ b/resources/views/chapters/form.blade.php @@ -11,6 +11,15 @@ @include('form/textarea', ['name' => 'description']) +
+
+ +
+
+ @include('components.tag-manager', ['entity' => isset($chapter)?$chapter:null, 'entityType' => 'chapter']) +
+
+
{{ trans('common.cancel') }} diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 62bff243b..ea9820022 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -91,6 +91,15 @@
+ @if($chapter->tags->count() > 0) +
+

@icon('tag') {{ trans('entities.chapter_tags') }}

+
+ @include('components.tag-list', ['entity' => $chapter]) +
+
+ @endif + @include('partials/book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree]) @stop diff --git a/resources/views/components/tag-list.blade.php b/resources/views/components/tag-list.blade.php new file mode 100644 index 000000000..9f4273c5a --- /dev/null +++ b/resources/views/components/tag-list.blade.php @@ -0,0 +1,10 @@ + + + @foreach($entity->tags as $tag) + + + @if($tag->value) @endif + + @endforeach + +
value) colspan="2" @endif>{{ $tag->name }}{{$tag->value}}
\ No newline at end of file diff --git a/resources/views/components/tag-manager.blade.php b/resources/views/components/tag-manager.blade.php new file mode 100644 index 000000000..801919a14 --- /dev/null +++ b/resources/views/components/tag-manager.blade.php @@ -0,0 +1,23 @@ +
+
+

{!! nl2br(e(trans('entities.tags_explain'))) !!}

+ + + +
+
@icon('grip')
+
+ +
+
+ +
+
@icon('close')
+
+
+ + +
+
\ No newline at end of file diff --git a/resources/views/pages/form-toolbox.blade.php b/resources/views/pages/form-toolbox.blade.php index dd847f297..f6ee2510d 100644 --- a/resources/views/pages/form-toolbox.blade.php +++ b/resources/views/pages/form-toolbox.blade.php @@ -9,29 +9,10 @@ @endif -
+

{{ trans('entities.page_tags') }}

-
-

{!! nl2br(e(trans('entities.tags_explain'))) !!}

- - - -
-
@icon('grip')
-
- -
-
- -
-
@icon('close')
-
-
- - - +
+ @include('components.tag-manager', ['entity' => $page, 'entityType' => 'page'])
diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index f11da0f4f..a6c4f329d 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -81,16 +81,7 @@

@icon('tag') {{ trans('entities.page_tags') }}

- - - @foreach($page->tags as $tag) - - - @if($tag->value) @endif - - @endforeach - -
value) colspan="2" @endif>{{ $tag->name }}{{$tag->value}}
+ @include('components.tag-list', ['entity' => $page])
@endif diff --git a/tests/Entity/TagTest.php b/tests/Entity/TagTest.php index 1ef7b7bde..7e1166388 100644 --- a/tests/Entity/TagTest.php +++ b/tests/Entity/TagTest.php @@ -1,6 +1,7 @@ defaultTagCount)->make(); } - $page->tags()->saveMany($tags); - return $page; + $entity->tags()->saveMany($tags); + return $entity; } public function test_get_page_tags() { - $page = $this->getPageWithTags(); + $page = $this->getEntityWithTags(Page::class); // Add some other tags to check they don't interfere factory(Tag::class, $this->defaultTagCount)->create(); @@ -41,6 +42,34 @@ class TagTest extends BrowserKitTest $this->assertTrue(count($json) === $this->defaultTagCount, "Returned JSON item count is not as expected"); } + public function test_get_chapter_tags() + { + $chapter = $this->getEntityWithTags(Chapter::class); + + // Add some other tags to check they don't interfere + factory(Tag::class, $this->defaultTagCount)->create(); + + $this->asAdmin()->get("/ajax/tags/get/chapter/" . $chapter->id) + ->shouldReturnJson(); + + $json = json_decode($this->response->getContent()); + $this->assertTrue(count($json) === $this->defaultTagCount, "Returned JSON item count is not as expected"); + } + + public function test_get_book_tags() + { + $book = $this->getEntityWithTags(Book::class); + + // Add some other tags to check they don't interfere + factory(Tag::class, $this->defaultTagCount)->create(); + + $this->asAdmin()->get("/ajax/tags/get/book/" . $book->id) + ->shouldReturnJson(); + + $json = json_decode($this->response->getContent()); + $this->assertTrue(count($json) === $this->defaultTagCount, "Returned JSON item count is not as expected"); + } + public function test_tag_name_suggestions() { // Create some tags with similar names to test with @@ -51,7 +80,7 @@ class TagTest extends BrowserKitTest $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'county'])); $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'planet'])); $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'plans'])); - $page = $this->getPageWithTags($attrs); + $page = $this->getEntityWithTags(Page::class, $attrs); $this->asAdmin()->get('/ajax/tags/suggest/names?search=dog')->seeJsonEquals([]); $this->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country', 'county']); @@ -69,7 +98,7 @@ class TagTest extends BrowserKitTest $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'county', 'value' => 'dog'])); $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'planet', 'value' => 'catapult'])); $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'plans', 'value' => 'dodgy'])); - $page = $this->getPageWithTags($attrs); + $page = $this->getEntityWithTags(Page::class, $attrs); $this->asAdmin()->get('/ajax/tags/suggest/values?search=ora')->seeJsonEquals([]); $this->get('/ajax/tags/suggest/values?search=cat')->seeJsonEquals(['cats', 'cattery', 'catapult']); @@ -85,7 +114,7 @@ class TagTest extends BrowserKitTest $attrs = collect(); $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'country'])); $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'color'])); - $page = $this->getPageWithTags($attrs); + $page = $this->getEntityWithTags(Page::class, $attrs); $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']); $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']);