]> BookStack Code Mirror - bookstack/commitdiff
Renamed attribute to tags & continued interface
authorDan Brown <redacted>
Fri, 13 May 2016 20:20:21 +0000 (21:20 +0100)
committerDan Brown <redacted>
Fri, 13 May 2016 20:20:21 +0000 (21:20 +0100)
Also fixed page create route broken in last commit

18 files changed:
app/Entity.php
app/Http/Controllers/AttributeController.php [deleted file]
app/Http/Controllers/TagController.php [new file with mode: 0644]
app/Http/routes.php
app/Repos/PageRepo.php
app/Repos/TagRepo.php [moved from app/Repos/AttributeRepo.php with 60% similarity]
app/Tag.php [moved from app/Attribute.php with 77% similarity]
database/factories/ModelFactory.php
database/migrations/2016_05_06_185215_create_tags_table.php [moved from database/migrations/2016_05_06_185215_create_attributes_table.php with 83% similarity]
resources/assets/js/controllers.js
resources/assets/sass/_forms.scss
resources/assets/sass/_tables.scss
resources/assets/sass/styles.scss
resources/views/pages/edit.blade.php
resources/views/pages/form-toolbox.blade.php [new file with mode: 0644]
resources/views/partials/custom-styles.blade.php
tests/Entity/AttributeTests.php [deleted file]
tests/Entity/TagTests.php [new file with mode: 0644]

index 79ff9ff38616ba3b8d849e37d52d958ce9a2b90d..7347ddcd689457e5eb1726df1d02a545f56a6ab2 100644 (file)
@@ -55,12 +55,12 @@ class Entity extends Ownable
     }
 
     /**
-     * Get the Attribute models that have been user assigned to this entity.
+     * Get the Tag models that have been user assigned to this entity.
      * @return \Illuminate\Database\Eloquent\Relations\MorphMany
      */
-    public function attributes()
+    public function tags()
     {
-        return $this->morphMany(Attribute::class, 'entity');
+        return $this->morphMany(Tag::class, 'entity');
     }
 
     /**
diff --git a/app/Http/Controllers/AttributeController.php b/app/Http/Controllers/AttributeController.php
deleted file mode 100644 (file)
index 6e69137..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php namespace BookStack\Http\Controllers;
-
-use BookStack\Repos\AttributeRepo;
-use Illuminate\Http\Request;
-use BookStack\Http\Requests;
-
-class AttributeController extends Controller
-{
-
-    protected $attributeRepo;
-
-    /**
-     * AttributeController constructor.
-     * @param $attributeRepo
-     */
-    public function __construct(AttributeRepo $attributeRepo)
-    {
-        $this->attributeRepo = $attributeRepo;
-    }
-
-    /**
-     * Get all the Attributes for a particular entity
-     * @param $entityType
-     * @param $entityId
-     */
-    public function getForEntity($entityType, $entityId)
-    {
-        $attributes = $this->attributeRepo->getForEntity($entityType, $entityId);
-        return response()->json($attributes);
-    }
-
-    /**
-     * Update the attributes for a particular entity.
-     * @param $entityType
-     * @param $entityId
-     * @param Request $request
-     * @return mixed
-     */
-    public function updateForEntity($entityType, $entityId, Request $request)
-    {
-        $entity = $this->attributeRepo->getEntity($entityType, $entityId, 'update');
-        if ($entity === null) return $this->jsonError("Entity not found", 404);
-
-        $inputAttributes = $request->input('attributes');
-        $attributes = $this->attributeRepo->saveAttributesToEntity($entity, $inputAttributes);
-        return response()->json([
-            'attributes' => $attributes,
-            'message' => 'Attributes successfully updated'
-        ]);
-    }
-
-    /**
-     * Get attribute name suggestions from a given search term.
-     * @param Request $request
-     */
-    public function getNameSuggestions(Request $request)
-    {
-        $searchTerm = $request->get('search');
-        $suggestions = $this->attributeRepo->getNameSuggestions($searchTerm);
-        return response()->json($suggestions);
-    }
-
-
-}
diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php
new file mode 100644 (file)
index 0000000..e236986
--- /dev/null
@@ -0,0 +1,64 @@
+<?php namespace BookStack\Http\Controllers;
+
+use BookStack\Repos\TagRepo;
+use Illuminate\Http\Request;
+use BookStack\Http\Requests;
+
+class TagController extends Controller
+{
+
+    protected $tagRepo;
+
+    /**
+     * TagController constructor.
+     * @param $tagRepo
+     */
+    public function __construct(TagRepo $tagRepo)
+    {
+        $this->tagRepo = $tagRepo;
+    }
+
+    /**
+     * Get all the Tags for a particular entity
+     * @param $entityType
+     * @param $entityId
+     */
+    public function getForEntity($entityType, $entityId)
+    {
+        $tags = $this->tagRepo->getForEntity($entityType, $entityId);
+        return response()->json($tags);
+    }
+
+    /**
+     * Update the tags for a particular entity.
+     * @param $entityType
+     * @param $entityId
+     * @param Request $request
+     * @return mixed
+     */
+    public function updateForEntity($entityType, $entityId, Request $request)
+    {
+        $entity = $this->tagRepo->getEntity($entityType, $entityId, 'update');
+        if ($entity === null) return $this->jsonError("Entity not found", 404);
+
+        $inputTags = $request->input('tags');
+        $tags = $this->tagRepo->saveTagsToEntity($entity, $inputTags);
+        return response()->json([
+            'tags' => $tags,
+            'message' => 'Tags successfully updated'
+        ]);
+    }
+
+    /**
+     * Get tag name suggestions from a given search term.
+     * @param Request $request
+     */
+    public function getNameSuggestions(Request $request)
+    {
+        $searchTerm = $request->get('search');
+        $suggestions = $this->tagRepo->getNameSuggestions($searchTerm);
+        return response()->json($suggestions);
+    }
+
+
+}
index 7c6911b2e74df0448708066fbf9aa17c40e1ac70..629f61ba9c0cb3f090391c62011d86702368465d 100644 (file)
@@ -28,7 +28,7 @@ Route::group(['middleware' => 'auth'], function () {
         // Pages
         Route::get('/{bookSlug}/page/create', 'PageController@create');
         Route::get('/{bookSlug}/draft/{pageId}', 'PageController@editDraft');
-        Route::post('/{bookSlug}/page/{pageId}', 'PageController@store');
+        Route::post('/{bookSlug}/draft/{pageId}', 'PageController@store');
         Route::get('/{bookSlug}/page/{pageSlug}', 'PageController@show');
         Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageController@exportPdf');
         Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageController@exportHtml');
@@ -85,11 +85,11 @@ Route::group(['middleware' => 'auth'], function () {
     Route::get('/ajax/page/{id}', 'PageController@getPageAjax');
     Route::delete('/ajax/page/{id}', 'PageController@ajaxDestroy');
 
-    // Attribute routes (AJAX)
-    Route::group(['prefix' => 'ajax/attributes'], function() {
-        Route::get('/get/{entityType}/{entityId}', 'AttributeController@getForEntity');
-        Route::get('/suggest', 'AttributeController@getNameSuggestions');
-        Route::post('/update/{entityType}/{entityId}', 'AttributeController@updateForEntity');
+    // Tag routes (AJAX)
+    Route::group(['prefix' => 'ajax/tags'], function() {
+        Route::get('/get/{entityType}/{entityId}', 'TagController@getForEntity');
+        Route::get('/suggest', 'TagController@getNameSuggestions');
+        Route::post('/update/{entityType}/{entityId}', 'TagController@updateForEntity');
     });
 
     // Links
index ef50b7181123a2bd80429646d5e436545e3b0892..938c0f5ae756aa8ea9a84eef9070cf29b6ff9f8f 100644 (file)
@@ -582,7 +582,7 @@ class PageRepo extends EntityRepo
     {
         Activity::removeEntity($page);
         $page->views()->delete();
-        $page->attributes()->delete();
+        $page->tags()->delete();
         $page->revisions()->delete();
         $page->permissions()->delete();
         $this->permissionService->deleteJointPermissionsForEntity($page);
similarity index 60%
rename from app/Repos/AttributeRepo.php
rename to app/Repos/TagRepo.php
index fb742b2d874286b8d6ef1797fd2fff66ca2b3a29..5d3670e6f0e78a71cbee3a7fda4ce137f26e5454 100644 (file)
@@ -1,29 +1,29 @@
 <?php namespace BookStack\Repos;
 
-use BookStack\Attribute;
+use BookStack\Tag;
 use BookStack\Entity;
 use BookStack\Services\PermissionService;
 
 /**
- * Class AttributeRepo
+ * Class TagRepo
  * @package BookStack\Repos
  */
-class AttributeRepo
+class TagRepo
 {
 
-    protected $attribute;
+    protected $tag;
     protected $entity;
     protected $permissionService;
 
     /**
-     * AttributeRepo constructor.
-     * @param Attribute $attr
+     * TagRepo constructor.
+     * @param Tag $attr
      * @param Entity $ent
      * @param PermissionService $ps
      */
-    public function __construct(Attribute $attr, Entity $ent, PermissionService $ps)
+    public function __construct(Tag $attr, Entity $ent, PermissionService $ps)
     {
-        $this->attribute = $attr;
+        $this->tag = $attr;
         $this->entity = $ent;
         $this->permissionService = $ps;
     }
@@ -37,13 +37,13 @@ class AttributeRepo
     public function getEntity($entityType, $entityId, $action = 'view')
     {
         $entityInstance = $this->entity->getEntityInstance($entityType);
-        $searchQuery = $entityInstance->where('id', '=', $entityId)->with('attributes');
+        $searchQuery = $entityInstance->where('id', '=', $entityId)->with('tags');
         $searchQuery = $this->permissionService->enforceEntityRestrictions($searchQuery, $action);
         return $searchQuery->first();
     }
 
     /**
-     * Get all attributes for a particular entity.
+     * Get all tags for a particular entity.
      * @param string $entityType
      * @param int $entityId
      * @return mixed
@@ -53,42 +53,42 @@ class AttributeRepo
         $entity = $this->getEntity($entityType, $entityId);
         if ($entity === null) return collect();
 
-        return $entity->attributes;
+        return $entity->tags;
     }
 
     /**
-     * Get attribute name suggestions from scanning existing attribute names.
+     * Get tag name suggestions from scanning existing tag names.
      * @param $searchTerm
      * @return array
      */
     public function getNameSuggestions($searchTerm)
     {
         if ($searchTerm === '') return [];
-        $query = $this->attribute->where('name', 'LIKE', $searchTerm . '%')->groupBy('name')->orderBy('name', 'desc');
-        $query = $this->permissionService->filterRestrictedEntityRelations($query, 'attributes', 'entity_id', 'entity_type');
+        $query = $this->tag->where('name', 'LIKE', $searchTerm . '%')->groupBy('name')->orderBy('name', 'desc');
+        $query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
         return $query->get(['name'])->pluck('name');
     }
 
     /**
-     * Save an array of attributes to an entity
+     * Save an array of tags to an entity
      * @param Entity $entity
-     * @param array $attributes
+     * @param array $tags
      * @return array|\Illuminate\Database\Eloquent\Collection
      */
-    public function saveAttributesToEntity(Entity $entity, $attributes = [])
+    public function saveTagsToEntity(Entity $entity, $tags = [])
     {
-        $entity->attributes()->delete();
-        $newAttributes = [];
-        foreach ($attributes as $attribute) {
-            if (trim($attribute['name']) === '') continue;
-            $newAttributes[] = $this->newInstanceFromInput($attribute);
+        $entity->tags()->delete();
+        $newTags = [];
+        foreach ($tags as $tag) {
+            if (trim($tag['name']) === '') continue;
+            $newTags[] = $this->newInstanceFromInput($tag);
         }
 
-        return $entity->attributes()->saveMany($newAttributes);
+        return $entity->tags()->saveMany($newTags);
     }
 
     /**
-     * Create a new Attribute instance from user input.
+     * Create a new Tag instance from user input.
      * @param $input
      * @return static
      */
@@ -98,7 +98,7 @@ class AttributeRepo
         $value = isset($input['value']) ? trim($input['value']) : '';
         // Any other modification or cleanup required can go here
         $values = ['name' => $name, 'value' => $value];
-        return $this->attribute->newInstance($values);
+        return $this->tag->newInstance($values);
     }
 
 }
\ No newline at end of file
similarity index 77%
rename from app/Attribute.php
rename to app/Tag.php
index 3fe201a425c1dd0515f83fea4a821571c26da65f..2eeb0b1f99115aef4ddb104aa41b99821bdd5436 100644 (file)
@@ -4,12 +4,12 @@
  * Class Attribute
  * @package BookStack
  */
-class Attribute extends Model
+class Tag extends Model
 {
     protected $fillable = ['name', 'value', 'order'];
 
     /**
-     * Get the entity that this attribute belongs to
+     * Get the entity that this tag belongs to
      * @return \Illuminate\Database\Eloquent\Relations\MorphTo
      */
     public function entity()
index a1a6a92a034be21b50def2896be2cc9d24a3135e..3820d5b59eeb90e490d1bf41e8bae11067da8850 100644 (file)
@@ -54,7 +54,7 @@ $factory->define(BookStack\Role::class, function ($faker) {
     ];
 });
 
-$factory->define(BookStack\Attribute::class, function ($faker) {
+$factory->define(BookStack\Tag::class, function ($faker) {
     return [
         'name' => $faker->city,
         'value' => $faker->sentence(3)
similarity index 83%
rename from database/migrations/2016_05_06_185215_create_attributes_table.php
rename to database/migrations/2016_05_06_185215_create_tags_table.php
index b6412037cbedbfe3c253c0384b95698182513d5d..55eed60606fef83bd9e89011bba4db2ac73e187e 100644 (file)
@@ -3,7 +3,7 @@
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Database\Migrations\Migration;
 
-class CreateAttributesTable extends Migration
+class CreateTagsTable extends Migration
 {
     /**
      * Run the migrations.
@@ -12,7 +12,7 @@ class CreateAttributesTable extends Migration
      */
     public function up()
     {
-        Schema::create('attributes', function (Blueprint $table) {
+        Schema::create('tags', function (Blueprint $table) {
             $table->increments('id');
             $table->integer('entity_id');
             $table->string('entity_type', 100);
@@ -35,6 +35,6 @@ class CreateAttributesTable extends Migration
      */
     public function down()
     {
-        Schema::drop('attributes');
+        Schema::drop('tags');
     }
 }
index e73efaac9cacf799729ba164d81f26a89843efc4..bec96f319b68cb41ff5a6ad307b969b5cd635245 100644 (file)
@@ -400,75 +400,75 @@ module.exports = function (ngApp, events) {
 
     }]);
 
-    ngApp.controller('PageAttributeController', ['$scope', '$http', '$attrs',
+    ngApp.controller('PageTagController', ['$scope', '$http', '$attrs',
         function ($scope, $http, $attrs) {
 
             const pageId = Number($attrs.pageId);
-            $scope.attributes = [];
+            $scope.tags = [];
 
             /**
-             * Push an empty attribute to the end of the scope attributes.
+             * Push an empty tag to the end of the scope tags.
              */
-            function addEmptyAttribute() {
-                $scope.attributes.push({
+            function addEmptyTag() {
+                $scope.tags.push({
                     name: '',
                     value: ''
                 });
             }
 
             /**
-             * Get all attributes for the current book and add into scope.
+             * Get all tags for the current book and add into scope.
              */
-            function getAttributes() {
-                $http.get('/ajax/attributes/get/page/' + pageId).then((responseData) => {
-                    $scope.attributes = responseData.data;
-                    addEmptyAttribute();
+            function getTags() {
+                $http.get('/ajax/tags/get/page/' + pageId).then((responseData) => {
+                    $scope.tags = responseData.data;
+                    addEmptyTag();
                 });
             }
-            getAttributes();
+            getTags();
 
             /**
-             * Set the order property on all attributes.
+             * Set the order property on all tags.
              */
-            function setAttributeOrder() {
-                for (let i = 0; i < $scope.attributes.length; i++) {
-                    $scope.attributes[i].order = i;
+            function setTagOrder() {
+                for (let i = 0; i < $scope.tags.length; i++) {
+                    $scope.tags[i].order = i;
                 }
             }
 
             /**
-             * When an attribute changes check if another empty editable
+             * When an tag changes check if another empty editable
              * field needs to be added onto the end.
-             * @param attribute
+             * @param tag
              */
-            $scope.attributeChange = function(attribute) {
-                let cPos = $scope.attributes.indexOf(attribute);
-                if (cPos !== $scope.attributes.length-1) return;
+            $scope.tagChange = function(tag) {
+                let cPos = $scope.tags.indexOf(tag);
+                if (cPos !== $scope.tags.length-1) return;
 
-                if (attribute.name !== '' || attribute.value !== '') {
-                    addEmptyAttribute();
+                if (tag.name !== '' || tag.value !== '') {
+                    addEmptyTag();
                 }
             };
 
             /**
-             * When an attribute field loses focus check the attribute to see if its
+             * When an tag field loses focus check the tag to see if its
              * empty and therefore could be removed from the list.
-             * @param attribute
+             * @param tag
              */
-            $scope.attributeBlur = function(attribute) {
-                let isLast = $scope.attributes.length - 1 === $scope.attributes.indexOf(attribute);
-                if (attribute.name === '' && attribute.value === '' && !isLast) {
-                    let cPos = $scope.attributes.indexOf(attribute);
-                    $scope.attributes.splice(cPos, 1);
+            $scope.tagBlur = function(tag) {
+                let isLast = $scope.tags.length - 1 === $scope.tags.indexOf(tag);
+                if (tag.name === '' && tag.value === '' && !isLast) {
+                    let cPos = $scope.tags.indexOf(tag);
+                    $scope.tags.splice(cPos, 1);
                 }
             };
 
-            $scope.saveAttributes = function() {
-                setAttributeOrder();
-                let postData = {attributes: $scope.attributes};
-                $http.post('/ajax/attributes/update/page/' + pageId, postData).then((responseData) => {
-                    $scope.attributes = responseData.data.attributes;
-                    addEmptyAttribute();
+            $scope.saveTags = function() {
+                setTagOrder();
+                let postData = {tags: $scope.tags};
+                $http.post('/ajax/tags/update/page/' + pageId, postData).then((responseData) => {
+                    $scope.tags = responseData.data.tags;
+                    addEmptyTag();
                     events.emit('success', responseData.data.message);
                 })
             };
index 482cf54bd8720e0f10fa11025e2ed54c023d5a58..4b50f6022f70e5176100c608c2644e00c1ff8680 100644 (file)
@@ -239,6 +239,17 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] {
   }
 }
 
+input.outline {
+  border: 0;
+  border-bottom: 2px solid #DDD;
+  border-radius: 0;
+  &:focus, &:active {
+    border: 0;
+    border-bottom: 2px solid #AAA;
+    outline: 0;
+  }
+}
+
 #login-form label[for="remember"] {
   margin: 0;
 }
index e6ec76b38e40370b701719df57e7ab53816a4e5f..b23db436aeba9c63aefb67c82b6c992c21c2c7f1 100644 (file)
@@ -26,6 +26,13 @@ table {
   }
 }
 
+table.no-style {
+  td {
+    border: 0;
+    padding: 0;
+  }
+}
+
 table.list-table {
   margin: 0 -$-xs;
   td {
index 4b6b1cc4637f1606ceaa82e749a45682c8a6cdbb..b48b7dd4e4b8e966aa47fe1d7b1022dfd9e53e09 100644 (file)
@@ -208,11 +208,70 @@ $btt-size: 40px;
   background-color: #FFF;
   border: 1px solid #BBB;
   border-radius: 3px;
-  padding: $-l;
   position: fixed;
   right: $-xl*2;
   top: 100px;
   z-index: 99;
   height: 800px;
+  width: 480px;
   overflow-y: scroll;
+  display: flex;
+  align-items: stretch;
+  flex-direction: row;
+  > div {
+    flex: 1;
+    position: relative;
+  }
+  .tabs {
+    display: block;
+    border-right: 1px solid #DDD;
+    width: 54px;
+    flex: 0;
+  }
+  .tabs i {
+    padding: 0;
+    margin: 0;
+  }
+  .tabs [tab-button] {
+    display: block;
+    cursor: pointer;
+    color: #666;
+    padding: $-m;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.3);
+    &.active {
+      color: #444;
+      background-color: rgba(0, 0, 0, 0.1);
+    }
+  }
+  div[tab-content] .padded {
+    padding: 0 $-m;
+  }
+  h4 {
+    font-size: 24px;
+    margin: $-m 0 0 0;
+    padding: 0 $-m;
+  }
+  .tags input {
+    max-width: 100%;
+    width: 100%;
+    min-width: 50px;
+  }
+  .tags td {
+    padding-right: $-s;
+    padding-top: $-s;
+  }
+  button.pos {
+    position: absolute;
+    bottom: 0;
+    display: block;
+    width: 100%;
+    padding: $-s;
+    border: 0;
+    margin: 0;
+    box-shadow: none;
+    border-radius: 0;
+    &:hover{
+      box-shadow: none;
+    }
+  }
 }
\ No newline at end of file
index a536ad23ea4b2d3f33f453babf0707d0b30a5740..d5cb1d3e3dd1b027da7c4f42c3d63d1e222045d8 100644 (file)
             @include('pages/form', ['model' => $page])
         </form>
 
-        <div class="floating-toolbox" ng-controller="PageAttributeController" page-id="{{ $page->id or 0 }}">
-            <form ng-submit="saveAttributes()">
-                <table>
-                    <tr ng-repeat="attribute in attributes">
-                        <td><input type="text" ng-model="attribute.name" ng-change="attributeChange(attribute)" ng-blur="attributeBlur(attribute)" placeholder="Attribute Name"></td>
-                        <td><input type="text" ng-model="attribute.value" ng-change="attributeChange(attribute)" ng-blur="attributeBlur(attribute)" placeholder="Value"></td>
-                    </tr>
-                </table>
-                <button class="button pos" type="submit">Save attributes</button>
-            </form>
-        </div>
+        @include('pages/form-toolbox')
 
     </div>
     @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
diff --git a/resources/views/pages/form-toolbox.blade.php b/resources/views/pages/form-toolbox.blade.php
new file mode 100644 (file)
index 0000000..d223f17
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="floating-toolbox">
+    <div class="tabs primary-background-light">
+        <span tab-button class="active"><i class="zmdi zmdi-tag"></i></span>
+        <span tab-button><i class="zmdi zmdi-wrench"></i></span>
+    </div>
+    <div tab-content ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
+        <form ng-submit="saveTags()" >
+            <h4>Page Tags</h4>
+            <div class="padded tags">
+                <table class="no-style" style="width: 100%;">
+                    <tr ng-repeat="tag in tags">
+                        <td><input class="outline" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td>
+                        <td><input class="outline" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td>
+                    </tr>
+                </table>
+            </div>
+            <button class="button pos" type="submit">Save Tags</button>
+        </form>
+    </div>
+</div>
\ No newline at end of file
index de324d28433774af2e359e582ee0329104ec8ae0..c5937a2b137d12be90cc0f37fc8e2508b0643bf2 100644 (file)
@@ -1,9 +1,9 @@
 @if(Setting::get('app-color'))
     <style>
-        header, #back-to-top {
+        header, #back-to-top, .primary-background {
             background-color: {{ Setting::get('app-color') }};
         }
-        .faded-small {
+        .faded-small, .primary-background-light {
             background-color: {{ Setting::get('app-color-light') }};
         }
         .button-base, .button, input[type="button"], input[type="submit"] {
diff --git a/tests/Entity/AttributeTests.php b/tests/Entity/AttributeTests.php
deleted file mode 100644 (file)
index 8087660..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php namespace Entity;
-
-use BookStack\Attribute;
-use BookStack\Page;
-use BookStack\Services\PermissionService;
-
-class AttributeTests extends \TestCase
-{
-
-    protected $defaultAttrCount = 20;
-
-    /**
-     * Get an instance of a page that has many attributes.
-     * @param Attribute[]|bool $attributes
-     * @return mixed
-     */
-    protected function getPageWithAttributes($attributes = false)
-    {
-        $page = Page::first();
-
-        if (!$attributes) {
-            $attributes = factory(Attribute::class, $this->defaultAttrCount)->make();
-        }
-
-        $page->attributes()->saveMany($attributes);
-        return $page;
-    }
-
-    public function test_get_page_attributes()
-    {
-        $page = $this->getPageWithAttributes();
-
-        // Add some other attributes to check they don't interfere
-        factory(Attribute::class, $this->defaultAttrCount)->create();
-
-        $this->asAdmin()->get("/ajax/attributes/get/page/" . $page->id)
-            ->shouldReturnJson();
-
-        $json = json_decode($this->response->getContent());
-        $this->assertTrue(count($json) === $this->defaultAttrCount, "Returned JSON item count is not as expected");
-    }
-
-    public function test_attribute_name_suggestions()
-    {
-        // Create some attributes with similar names to test with
-        $attrs = collect();
-        $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'country']));
-        $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'color']));
-        $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'city']));
-        $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'county']));
-        $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'planet']));
-        $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'plans']));
-        $page = $this->getPageWithAttributes($attrs);
-
-        $this->asAdmin()->get('/ajax/attributes/suggest?search=dog')->seeJsonEquals([]);
-        $this->get('/ajax/attributes/suggest?search=co')->seeJsonEquals(['color', 'country', 'county']);
-        $this->get('/ajax/attributes/suggest?search=cou')->seeJsonEquals(['country', 'county']);
-        $this->get('/ajax/attributes/suggest?search=pla')->seeJsonEquals(['planet', 'plans']);
-    }
-
-    public function test_entity_permissions_effect_attribute_suggestions()
-    {
-        $permissionService = $this->app->make(PermissionService::class);
-
-        // Create some attributes with similar names to test with and save to a page
-        $attrs = collect();
-        $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'country']));
-        $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'color']));
-        $page = $this->getPageWithAttributes($attrs);
-
-        $this->asAdmin()->get('/ajax/attributes/suggest?search=co')->seeJsonEquals(['color', 'country']);
-        $this->asEditor()->get('/ajax/attributes/suggest?search=co')->seeJsonEquals(['color', 'country']);
-
-        // Set restricted permission the page
-        $page->restricted = true;
-        $page->save();
-        $permissionService->buildJointPermissionsForEntity($page);
-
-        $this->asAdmin()->get('/ajax/attributes/suggest?search=co')->seeJsonEquals(['color', 'country']);
-        $this->asEditor()->get('/ajax/attributes/suggest?search=co')->seeJsonEquals([]);
-    }
-
-    public function test_entity_attribute_updating()
-    {
-        $page = $this->getPageWithAttributes();
-
-        $testJsonData = [
-            ['name' => 'color', 'value' => 'red'],
-            ['name' => 'color', 'value' => ' blue '],
-            ['name' => 'city', 'value' => 'London '],
-            ['name' => 'country', 'value' => ' England'],
-        ];
-        $testResponseJsonData = [
-            ['name' => 'color', 'value' => 'red'],
-            ['name' => 'color', 'value' => 'blue'],
-            ['name' => 'city', 'value' => 'London'],
-            ['name' => 'country', 'value' => 'England'],
-        ];
-
-        // Do update request
-        $this->asAdmin()->json("POST", "/ajax/attributes/update/page/" . $page->id, ['attributes' => $testJsonData]);
-        $updateData = json_decode($this->response->getContent());
-        // Check data is correct
-        $testDataCorrect = true;
-        foreach ($updateData->attributes as $data) {
-            $testItem = ['name' => $data->name, 'value' => $data->value];
-            if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false;
-        }
-        $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s";
-        $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($updateData)));
-        $this->assertTrue(isset($updateData->message), "No message returned in attribute update response");
-
-        // Do get request
-        $this->asAdmin()->get("/ajax/attributes/get/page/" . $page->id);
-        $getResponseData = json_decode($this->response->getContent());
-        // Check counts
-        $this->assertTrue(count($getResponseData) === count($testJsonData), "The received attribute count is incorrect");
-        // Check data is correct
-        $testDataCorrect = true;
-        foreach ($getResponseData as $data) {
-            $testItem = ['name' => $data->name, 'value' => $data->value];
-            if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false;
-        }
-        $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s";
-        $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($getResponseData)));
-    }
-
-}
diff --git a/tests/Entity/TagTests.php b/tests/Entity/TagTests.php
new file mode 100644 (file)
index 0000000..3793864
--- /dev/null
@@ -0,0 +1,128 @@
+<?php namespace Entity;
+
+use BookStack\Tag;
+use BookStack\Page;
+use BookStack\Services\PermissionService;
+
+class TagTests extends \TestCase
+{
+
+    protected $defaultTagCount = 20;
+
+    /**
+     * Get an instance of a page that has many tags.
+     * @param Tag[]|bool $tags
+     * @return mixed
+     */
+    protected function getPageWithTags($tags = false)
+    {
+        $page = Page::first();
+
+        if (!$tags) {
+            $tags = factory(Tag::class, $this->defaultTagCount)->make();
+        }
+
+        $page->tags()->saveMany($tags);
+        return $page;
+    }
+
+    public function test_get_page_tags()
+    {
+        $page = $this->getPageWithTags();
+
+        // Add some other tags to check they don't interfere
+        factory(Tag::class, $this->defaultTagCount)->create();
+
+        $this->asAdmin()->get("/ajax/tags/get/page/" . $page->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
+        $attrs = collect();
+        $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'country']));
+        $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'color']));
+        $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'city']));
+        $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);
+
+        $this->asAdmin()->get('/ajax/tags/suggest?search=dog')->seeJsonEquals([]);
+        $this->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country', 'county']);
+        $this->get('/ajax/tags/suggest?search=cou')->seeJsonEquals(['country', 'county']);
+        $this->get('/ajax/tags/suggest?search=pla')->seeJsonEquals(['planet', 'plans']);
+    }
+
+    public function test_entity_permissions_effect_tag_suggestions()
+    {
+        $permissionService = $this->app->make(PermissionService::class);
+
+        // Create some tags with similar names to test with and save to a page
+        $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);
+
+        $this->asAdmin()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']);
+        $this->asEditor()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']);
+
+        // Set restricted permission the page
+        $page->restricted = true;
+        $page->save();
+        $permissionService->buildJointPermissionsForEntity($page);
+
+        $this->asAdmin()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']);
+        $this->asEditor()->get('/ajax/tags/suggest?search=co')->seeJsonEquals([]);
+    }
+
+    public function test_entity_tag_updating()
+    {
+        $page = $this->getPageWithTags();
+
+        $testJsonData = [
+            ['name' => 'color', 'value' => 'red'],
+            ['name' => 'color', 'value' => ' blue '],
+            ['name' => 'city', 'value' => 'London '],
+            ['name' => 'country', 'value' => ' England'],
+        ];
+        $testResponseJsonData = [
+            ['name' => 'color', 'value' => 'red'],
+            ['name' => 'color', 'value' => 'blue'],
+            ['name' => 'city', 'value' => 'London'],
+            ['name' => 'country', 'value' => 'England'],
+        ];
+
+        // Do update request
+        $this->asAdmin()->json("POST", "/ajax/tags/update/page/" . $page->id, ['tags' => $testJsonData]);
+        $updateData = json_decode($this->response->getContent());
+        // Check data is correct
+        $testDataCorrect = true;
+        foreach ($updateData->tags as $data) {
+            $testItem = ['name' => $data->name, 'value' => $data->value];
+            if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false;
+        }
+        $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s";
+        $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($updateData)));
+        $this->assertTrue(isset($updateData->message), "No message returned in tag update response");
+
+        // Do get request
+        $this->asAdmin()->get("/ajax/tags/get/page/" . $page->id);
+        $getResponseData = json_decode($this->response->getContent());
+        // Check counts
+        $this->assertTrue(count($getResponseData) === count($testJsonData), "The received tag count is incorrect");
+        // Check data is correct
+        $testDataCorrect = true;
+        foreach ($getResponseData as $data) {
+            $testItem = ['name' => $data->name, 'value' => $data->value];
+            if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false;
+        }
+        $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s";
+        $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($getResponseData)));
+    }
+
+}