Renamed attribute to tags & continued interface
Also fixed page create route broken in last commit
Showing
16 changed files
with
240 additions
and
153 deletions
| ... | @@ -55,12 +55,12 @@ class Entity extends Ownable | ... | @@ -55,12 +55,12 @@ class Entity extends Ownable |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | /** | 57 | /** |
| 58 | - * Get the Attribute models that have been user assigned to this entity. | 58 | + * Get the Tag models that have been user assigned to this entity. |
| 59 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany | 59 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany |
| 60 | */ | 60 | */ |
| 61 | - public function attributes() | 61 | + public function tags() |
| 62 | { | 62 | { |
| 63 | - return $this->morphMany(Attribute::class, 'entity'); | 63 | + return $this->morphMany(Tag::class, 'entity'); |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | /** | 66 | /** | ... | ... |
| 1 | <?php namespace BookStack\Http\Controllers; | 1 | <?php namespace BookStack\Http\Controllers; |
| 2 | 2 | ||
| 3 | -use BookStack\Repos\AttributeRepo; | 3 | +use BookStack\Repos\TagRepo; |
| 4 | use Illuminate\Http\Request; | 4 | use Illuminate\Http\Request; |
| 5 | use BookStack\Http\Requests; | 5 | use BookStack\Http\Requests; |
| 6 | 6 | ||
| 7 | -class AttributeController extends Controller | 7 | +class TagController extends Controller |
| 8 | { | 8 | { |
| 9 | 9 | ||
| 10 | - protected $attributeRepo; | 10 | + protected $tagRepo; |
| 11 | 11 | ||
| 12 | /** | 12 | /** |
| 13 | - * AttributeController constructor. | 13 | + * TagController constructor. |
| 14 | - * @param $attributeRepo | 14 | + * @param $tagRepo |
| 15 | */ | 15 | */ |
| 16 | - public function __construct(AttributeRepo $attributeRepo) | 16 | + public function __construct(TagRepo $tagRepo) |
| 17 | { | 17 | { |
| 18 | - $this->attributeRepo = $attributeRepo; | 18 | + $this->tagRepo = $tagRepo; |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | /** | 21 | /** |
| 22 | - * Get all the Attributes for a particular entity | 22 | + * Get all the Tags for a particular entity |
| 23 | * @param $entityType | 23 | * @param $entityType |
| 24 | * @param $entityId | 24 | * @param $entityId |
| 25 | */ | 25 | */ |
| 26 | public function getForEntity($entityType, $entityId) | 26 | public function getForEntity($entityType, $entityId) |
| 27 | { | 27 | { |
| 28 | - $attributes = $this->attributeRepo->getForEntity($entityType, $entityId); | 28 | + $tags = $this->tagRepo->getForEntity($entityType, $entityId); |
| 29 | - return response()->json($attributes); | 29 | + return response()->json($tags); |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | /** | 32 | /** |
| 33 | - * Update the attributes for a particular entity. | 33 | + * Update the tags for a particular entity. |
| 34 | * @param $entityType | 34 | * @param $entityType |
| 35 | * @param $entityId | 35 | * @param $entityId |
| 36 | * @param Request $request | 36 | * @param Request $request |
| ... | @@ -38,25 +38,25 @@ class AttributeController extends Controller | ... | @@ -38,25 +38,25 @@ class AttributeController extends Controller |
| 38 | */ | 38 | */ |
| 39 | public function updateForEntity($entityType, $entityId, Request $request) | 39 | public function updateForEntity($entityType, $entityId, Request $request) |
| 40 | { | 40 | { |
| 41 | - $entity = $this->attributeRepo->getEntity($entityType, $entityId, 'update'); | 41 | + $entity = $this->tagRepo->getEntity($entityType, $entityId, 'update'); |
| 42 | if ($entity === null) return $this->jsonError("Entity not found", 404); | 42 | if ($entity === null) return $this->jsonError("Entity not found", 404); |
| 43 | 43 | ||
| 44 | - $inputAttributes = $request->input('attributes'); | 44 | + $inputTags = $request->input('tags'); |
| 45 | - $attributes = $this->attributeRepo->saveAttributesToEntity($entity, $inputAttributes); | 45 | + $tags = $this->tagRepo->saveTagsToEntity($entity, $inputTags); |
| 46 | return response()->json([ | 46 | return response()->json([ |
| 47 | - 'attributes' => $attributes, | 47 | + 'tags' => $tags, |
| 48 | - 'message' => 'Attributes successfully updated' | 48 | + 'message' => 'Tags successfully updated' |
| 49 | ]); | 49 | ]); |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | /** | 52 | /** |
| 53 | - * Get attribute name suggestions from a given search term. | 53 | + * Get tag name suggestions from a given search term. |
| 54 | * @param Request $request | 54 | * @param Request $request |
| 55 | */ | 55 | */ |
| 56 | public function getNameSuggestions(Request $request) | 56 | public function getNameSuggestions(Request $request) |
| 57 | { | 57 | { |
| 58 | $searchTerm = $request->get('search'); | 58 | $searchTerm = $request->get('search'); |
| 59 | - $suggestions = $this->attributeRepo->getNameSuggestions($searchTerm); | 59 | + $suggestions = $this->tagRepo->getNameSuggestions($searchTerm); |
| 60 | return response()->json($suggestions); | 60 | return response()->json($suggestions); |
| 61 | } | 61 | } |
| 62 | 62 | ... | ... |
| ... | @@ -28,7 +28,7 @@ Route::group(['middleware' => 'auth'], function () { | ... | @@ -28,7 +28,7 @@ Route::group(['middleware' => 'auth'], function () { |
| 28 | // Pages | 28 | // Pages |
| 29 | Route::get('/{bookSlug}/page/create', 'PageController@create'); | 29 | Route::get('/{bookSlug}/page/create', 'PageController@create'); |
| 30 | Route::get('/{bookSlug}/draft/{pageId}', 'PageController@editDraft'); | 30 | Route::get('/{bookSlug}/draft/{pageId}', 'PageController@editDraft'); |
| 31 | - Route::post('/{bookSlug}/page/{pageId}', 'PageController@store'); | 31 | + Route::post('/{bookSlug}/draft/{pageId}', 'PageController@store'); |
| 32 | Route::get('/{bookSlug}/page/{pageSlug}', 'PageController@show'); | 32 | Route::get('/{bookSlug}/page/{pageSlug}', 'PageController@show'); |
| 33 | Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageController@exportPdf'); | 33 | Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageController@exportPdf'); |
| 34 | Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageController@exportHtml'); | 34 | Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageController@exportHtml'); |
| ... | @@ -85,11 +85,11 @@ Route::group(['middleware' => 'auth'], function () { | ... | @@ -85,11 +85,11 @@ Route::group(['middleware' => 'auth'], function () { |
| 85 | Route::get('/ajax/page/{id}', 'PageController@getPageAjax'); | 85 | Route::get('/ajax/page/{id}', 'PageController@getPageAjax'); |
| 86 | Route::delete('/ajax/page/{id}', 'PageController@ajaxDestroy'); | 86 | Route::delete('/ajax/page/{id}', 'PageController@ajaxDestroy'); |
| 87 | 87 | ||
| 88 | - // Attribute routes (AJAX) | 88 | + // Tag routes (AJAX) |
| 89 | - Route::group(['prefix' => 'ajax/attributes'], function() { | 89 | + Route::group(['prefix' => 'ajax/tags'], function() { |
| 90 | - Route::get('/get/{entityType}/{entityId}', 'AttributeController@getForEntity'); | 90 | + Route::get('/get/{entityType}/{entityId}', 'TagController@getForEntity'); |
| 91 | - Route::get('/suggest', 'AttributeController@getNameSuggestions'); | 91 | + Route::get('/suggest', 'TagController@getNameSuggestions'); |
| 92 | - Route::post('/update/{entityType}/{entityId}', 'AttributeController@updateForEntity'); | 92 | + Route::post('/update/{entityType}/{entityId}', 'TagController@updateForEntity'); |
| 93 | }); | 93 | }); |
| 94 | 94 | ||
| 95 | // Links | 95 | // Links | ... | ... |
| ... | @@ -582,7 +582,7 @@ class PageRepo extends EntityRepo | ... | @@ -582,7 +582,7 @@ class PageRepo extends EntityRepo |
| 582 | { | 582 | { |
| 583 | Activity::removeEntity($page); | 583 | Activity::removeEntity($page); |
| 584 | $page->views()->delete(); | 584 | $page->views()->delete(); |
| 585 | - $page->attributes()->delete(); | 585 | + $page->tags()->delete(); |
| 586 | $page->revisions()->delete(); | 586 | $page->revisions()->delete(); |
| 587 | $page->permissions()->delete(); | 587 | $page->permissions()->delete(); |
| 588 | $this->permissionService->deleteJointPermissionsForEntity($page); | 588 | $this->permissionService->deleteJointPermissionsForEntity($page); | ... | ... |
| 1 | <?php namespace BookStack\Repos; | 1 | <?php namespace BookStack\Repos; |
| 2 | 2 | ||
| 3 | -use BookStack\Attribute; | 3 | +use BookStack\Tag; |
| 4 | use BookStack\Entity; | 4 | use BookStack\Entity; |
| 5 | use BookStack\Services\PermissionService; | 5 | use BookStack\Services\PermissionService; |
| 6 | 6 | ||
| 7 | /** | 7 | /** |
| 8 | - * Class AttributeRepo | 8 | + * Class TagRepo |
| 9 | * @package BookStack\Repos | 9 | * @package BookStack\Repos |
| 10 | */ | 10 | */ |
| 11 | -class AttributeRepo | 11 | +class TagRepo |
| 12 | { | 12 | { |
| 13 | 13 | ||
| 14 | - protected $attribute; | 14 | + protected $tag; |
| 15 | protected $entity; | 15 | protected $entity; |
| 16 | protected $permissionService; | 16 | protected $permissionService; |
| 17 | 17 | ||
| 18 | /** | 18 | /** |
| 19 | - * AttributeRepo constructor. | 19 | + * TagRepo constructor. |
| 20 | - * @param Attribute $attr | 20 | + * @param Tag $attr |
| 21 | * @param Entity $ent | 21 | * @param Entity $ent |
| 22 | * @param PermissionService $ps | 22 | * @param PermissionService $ps |
| 23 | */ | 23 | */ |
| 24 | - public function __construct(Attribute $attr, Entity $ent, PermissionService $ps) | 24 | + public function __construct(Tag $attr, Entity $ent, PermissionService $ps) |
| 25 | { | 25 | { |
| 26 | - $this->attribute = $attr; | 26 | + $this->tag = $attr; |
| 27 | $this->entity = $ent; | 27 | $this->entity = $ent; |
| 28 | $this->permissionService = $ps; | 28 | $this->permissionService = $ps; |
| 29 | } | 29 | } |
| ... | @@ -37,13 +37,13 @@ class AttributeRepo | ... | @@ -37,13 +37,13 @@ class AttributeRepo |
| 37 | public function getEntity($entityType, $entityId, $action = 'view') | 37 | public function getEntity($entityType, $entityId, $action = 'view') |
| 38 | { | 38 | { |
| 39 | $entityInstance = $this->entity->getEntityInstance($entityType); | 39 | $entityInstance = $this->entity->getEntityInstance($entityType); |
| 40 | - $searchQuery = $entityInstance->where('id', '=', $entityId)->with('attributes'); | 40 | + $searchQuery = $entityInstance->where('id', '=', $entityId)->with('tags'); |
| 41 | $searchQuery = $this->permissionService->enforceEntityRestrictions($searchQuery, $action); | 41 | $searchQuery = $this->permissionService->enforceEntityRestrictions($searchQuery, $action); |
| 42 | return $searchQuery->first(); | 42 | return $searchQuery->first(); |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | /** | 45 | /** |
| 46 | - * Get all attributes for a particular entity. | 46 | + * Get all tags for a particular entity. |
| 47 | * @param string $entityType | 47 | * @param string $entityType |
| 48 | * @param int $entityId | 48 | * @param int $entityId |
| 49 | * @return mixed | 49 | * @return mixed |
| ... | @@ -53,42 +53,42 @@ class AttributeRepo | ... | @@ -53,42 +53,42 @@ class AttributeRepo |
| 53 | $entity = $this->getEntity($entityType, $entityId); | 53 | $entity = $this->getEntity($entityType, $entityId); |
| 54 | if ($entity === null) return collect(); | 54 | if ($entity === null) return collect(); |
| 55 | 55 | ||
| 56 | - return $entity->attributes; | 56 | + return $entity->tags; |
| 57 | } | 57 | } |
| 58 | 58 | ||
| 59 | /** | 59 | /** |
| 60 | - * Get attribute name suggestions from scanning existing attribute names. | 60 | + * Get tag name suggestions from scanning existing tag names. |
| 61 | * @param $searchTerm | 61 | * @param $searchTerm |
| 62 | * @return array | 62 | * @return array |
| 63 | */ | 63 | */ |
| 64 | public function getNameSuggestions($searchTerm) | 64 | public function getNameSuggestions($searchTerm) |
| 65 | { | 65 | { |
| 66 | if ($searchTerm === '') return []; | 66 | if ($searchTerm === '') return []; |
| 67 | - $query = $this->attribute->where('name', 'LIKE', $searchTerm . '%')->groupBy('name')->orderBy('name', 'desc'); | 67 | + $query = $this->tag->where('name', 'LIKE', $searchTerm . '%')->groupBy('name')->orderBy('name', 'desc'); |
| 68 | - $query = $this->permissionService->filterRestrictedEntityRelations($query, 'attributes', 'entity_id', 'entity_type'); | 68 | + $query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type'); |
| 69 | return $query->get(['name'])->pluck('name'); | 69 | return $query->get(['name'])->pluck('name'); |
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | /** | 72 | /** |
| 73 | - * Save an array of attributes to an entity | 73 | + * Save an array of tags to an entity |
| 74 | * @param Entity $entity | 74 | * @param Entity $entity |
| 75 | - * @param array $attributes | 75 | + * @param array $tags |
| 76 | * @return array|\Illuminate\Database\Eloquent\Collection | 76 | * @return array|\Illuminate\Database\Eloquent\Collection |
| 77 | */ | 77 | */ |
| 78 | - public function saveAttributesToEntity(Entity $entity, $attributes = []) | 78 | + public function saveTagsToEntity(Entity $entity, $tags = []) |
| 79 | { | 79 | { |
| 80 | - $entity->attributes()->delete(); | 80 | + $entity->tags()->delete(); |
| 81 | - $newAttributes = []; | 81 | + $newTags = []; |
| 82 | - foreach ($attributes as $attribute) { | 82 | + foreach ($tags as $tag) { |
| 83 | - if (trim($attribute['name']) === '') continue; | 83 | + if (trim($tag['name']) === '') continue; |
| 84 | - $newAttributes[] = $this->newInstanceFromInput($attribute); | 84 | + $newTags[] = $this->newInstanceFromInput($tag); |
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | - return $entity->attributes()->saveMany($newAttributes); | 87 | + return $entity->tags()->saveMany($newTags); |
| 88 | } | 88 | } |
| 89 | 89 | ||
| 90 | /** | 90 | /** |
| 91 | - * Create a new Attribute instance from user input. | 91 | + * Create a new Tag instance from user input. |
| 92 | * @param $input | 92 | * @param $input |
| 93 | * @return static | 93 | * @return static |
| 94 | */ | 94 | */ |
| ... | @@ -98,7 +98,7 @@ class AttributeRepo | ... | @@ -98,7 +98,7 @@ class AttributeRepo |
| 98 | $value = isset($input['value']) ? trim($input['value']) : ''; | 98 | $value = isset($input['value']) ? trim($input['value']) : ''; |
| 99 | // Any other modification or cleanup required can go here | 99 | // Any other modification or cleanup required can go here |
| 100 | $values = ['name' => $name, 'value' => $value]; | 100 | $values = ['name' => $name, 'value' => $value]; |
| 101 | - return $this->attribute->newInstance($values); | 101 | + return $this->tag->newInstance($values); |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | } | 104 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -4,12 +4,12 @@ | ... | @@ -4,12 +4,12 @@ |
| 4 | * Class Attribute | 4 | * Class Attribute |
| 5 | * @package BookStack | 5 | * @package BookStack |
| 6 | */ | 6 | */ |
| 7 | -class Attribute extends Model | 7 | +class Tag extends Model |
| 8 | { | 8 | { |
| 9 | protected $fillable = ['name', 'value', 'order']; | 9 | protected $fillable = ['name', 'value', 'order']; |
| 10 | 10 | ||
| 11 | /** | 11 | /** |
| 12 | - * Get the entity that this attribute belongs to | 12 | + * Get the entity that this tag belongs to |
| 13 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo | 13 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo |
| 14 | */ | 14 | */ |
| 15 | public function entity() | 15 | public function entity() | ... | ... |
| ... | @@ -54,7 +54,7 @@ $factory->define(BookStack\Role::class, function ($faker) { | ... | @@ -54,7 +54,7 @@ $factory->define(BookStack\Role::class, function ($faker) { |
| 54 | ]; | 54 | ]; |
| 55 | }); | 55 | }); |
| 56 | 56 | ||
| 57 | -$factory->define(BookStack\Attribute::class, function ($faker) { | 57 | +$factory->define(BookStack\Tag::class, function ($faker) { |
| 58 | return [ | 58 | return [ |
| 59 | 'name' => $faker->city, | 59 | 'name' => $faker->city, |
| 60 | 'value' => $faker->sentence(3) | 60 | 'value' => $faker->sentence(3) | ... | ... |
| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | use Illuminate\Database\Schema\Blueprint; | 3 | use Illuminate\Database\Schema\Blueprint; |
| 4 | use Illuminate\Database\Migrations\Migration; | 4 | use Illuminate\Database\Migrations\Migration; |
| 5 | 5 | ||
| 6 | -class CreateAttributesTable extends Migration | 6 | +class CreateTagsTable extends Migration |
| 7 | { | 7 | { |
| 8 | /** | 8 | /** |
| 9 | * Run the migrations. | 9 | * Run the migrations. |
| ... | @@ -12,7 +12,7 @@ class CreateAttributesTable extends Migration | ... | @@ -12,7 +12,7 @@ class CreateAttributesTable extends Migration |
| 12 | */ | 12 | */ |
| 13 | public function up() | 13 | public function up() |
| 14 | { | 14 | { |
| 15 | - Schema::create('attributes', function (Blueprint $table) { | 15 | + Schema::create('tags', function (Blueprint $table) { |
| 16 | $table->increments('id'); | 16 | $table->increments('id'); |
| 17 | $table->integer('entity_id'); | 17 | $table->integer('entity_id'); |
| 18 | $table->string('entity_type', 100); | 18 | $table->string('entity_type', 100); |
| ... | @@ -35,6 +35,6 @@ class CreateAttributesTable extends Migration | ... | @@ -35,6 +35,6 @@ class CreateAttributesTable extends Migration |
| 35 | */ | 35 | */ |
| 36 | public function down() | 36 | public function down() |
| 37 | { | 37 | { |
| 38 | - Schema::drop('attributes'); | 38 | + Schema::drop('tags'); |
| 39 | } | 39 | } |
| 40 | } | 40 | } | ... | ... |
| ... | @@ -400,75 +400,75 @@ module.exports = function (ngApp, events) { | ... | @@ -400,75 +400,75 @@ module.exports = function (ngApp, events) { |
| 400 | 400 | ||
| 401 | }]); | 401 | }]); |
| 402 | 402 | ||
| 403 | - ngApp.controller('PageAttributeController', ['$scope', '$http', '$attrs', | 403 | + ngApp.controller('PageTagController', ['$scope', '$http', '$attrs', |
| 404 | function ($scope, $http, $attrs) { | 404 | function ($scope, $http, $attrs) { |
| 405 | 405 | ||
| 406 | const pageId = Number($attrs.pageId); | 406 | const pageId = Number($attrs.pageId); |
| 407 | - $scope.attributes = []; | 407 | + $scope.tags = []; |
| 408 | 408 | ||
| 409 | /** | 409 | /** |
| 410 | - * Push an empty attribute to the end of the scope attributes. | 410 | + * Push an empty tag to the end of the scope tags. |
| 411 | */ | 411 | */ |
| 412 | - function addEmptyAttribute() { | 412 | + function addEmptyTag() { |
| 413 | - $scope.attributes.push({ | 413 | + $scope.tags.push({ |
| 414 | name: '', | 414 | name: '', |
| 415 | value: '' | 415 | value: '' |
| 416 | }); | 416 | }); |
| 417 | } | 417 | } |
| 418 | 418 | ||
| 419 | /** | 419 | /** |
| 420 | - * Get all attributes for the current book and add into scope. | 420 | + * Get all tags for the current book and add into scope. |
| 421 | */ | 421 | */ |
| 422 | - function getAttributes() { | 422 | + function getTags() { |
| 423 | - $http.get('/ajax/attributes/get/page/' + pageId).then((responseData) => { | 423 | + $http.get('/ajax/tags/get/page/' + pageId).then((responseData) => { |
| 424 | - $scope.attributes = responseData.data; | 424 | + $scope.tags = responseData.data; |
| 425 | - addEmptyAttribute(); | 425 | + addEmptyTag(); |
| 426 | }); | 426 | }); |
| 427 | } | 427 | } |
| 428 | - getAttributes(); | 428 | + getTags(); |
| 429 | 429 | ||
| 430 | /** | 430 | /** |
| 431 | - * Set the order property on all attributes. | 431 | + * Set the order property on all tags. |
| 432 | */ | 432 | */ |
| 433 | - function setAttributeOrder() { | 433 | + function setTagOrder() { |
| 434 | - for (let i = 0; i < $scope.attributes.length; i++) { | 434 | + for (let i = 0; i < $scope.tags.length; i++) { |
| 435 | - $scope.attributes[i].order = i; | 435 | + $scope.tags[i].order = i; |
| 436 | } | 436 | } |
| 437 | } | 437 | } |
| 438 | 438 | ||
| 439 | /** | 439 | /** |
| 440 | - * When an attribute changes check if another empty editable | 440 | + * When an tag changes check if another empty editable |
| 441 | * field needs to be added onto the end. | 441 | * field needs to be added onto the end. |
| 442 | - * @param attribute | 442 | + * @param tag |
| 443 | */ | 443 | */ |
| 444 | - $scope.attributeChange = function(attribute) { | 444 | + $scope.tagChange = function(tag) { |
| 445 | - let cPos = $scope.attributes.indexOf(attribute); | 445 | + let cPos = $scope.tags.indexOf(tag); |
| 446 | - if (cPos !== $scope.attributes.length-1) return; | 446 | + if (cPos !== $scope.tags.length-1) return; |
| 447 | 447 | ||
| 448 | - if (attribute.name !== '' || attribute.value !== '') { | 448 | + if (tag.name !== '' || tag.value !== '') { |
| 449 | - addEmptyAttribute(); | 449 | + addEmptyTag(); |
| 450 | } | 450 | } |
| 451 | }; | 451 | }; |
| 452 | 452 | ||
| 453 | /** | 453 | /** |
| 454 | - * When an attribute field loses focus check the attribute to see if its | 454 | + * When an tag field loses focus check the tag to see if its |
| 455 | * empty and therefore could be removed from the list. | 455 | * empty and therefore could be removed from the list. |
| 456 | - * @param attribute | 456 | + * @param tag |
| 457 | */ | 457 | */ |
| 458 | - $scope.attributeBlur = function(attribute) { | 458 | + $scope.tagBlur = function(tag) { |
| 459 | - let isLast = $scope.attributes.length - 1 === $scope.attributes.indexOf(attribute); | 459 | + let isLast = $scope.tags.length - 1 === $scope.tags.indexOf(tag); |
| 460 | - if (attribute.name === '' && attribute.value === '' && !isLast) { | 460 | + if (tag.name === '' && tag.value === '' && !isLast) { |
| 461 | - let cPos = $scope.attributes.indexOf(attribute); | 461 | + let cPos = $scope.tags.indexOf(tag); |
| 462 | - $scope.attributes.splice(cPos, 1); | 462 | + $scope.tags.splice(cPos, 1); |
| 463 | } | 463 | } |
| 464 | }; | 464 | }; |
| 465 | 465 | ||
| 466 | - $scope.saveAttributes = function() { | 466 | + $scope.saveTags = function() { |
| 467 | - setAttributeOrder(); | 467 | + setTagOrder(); |
| 468 | - let postData = {attributes: $scope.attributes}; | 468 | + let postData = {tags: $scope.tags}; |
| 469 | - $http.post('/ajax/attributes/update/page/' + pageId, postData).then((responseData) => { | 469 | + $http.post('/ajax/tags/update/page/' + pageId, postData).then((responseData) => { |
| 470 | - $scope.attributes = responseData.data.attributes; | 470 | + $scope.tags = responseData.data.tags; |
| 471 | - addEmptyAttribute(); | 471 | + addEmptyTag(); |
| 472 | events.emit('success', responseData.data.message); | 472 | events.emit('success', responseData.data.message); |
| 473 | }) | 473 | }) |
| 474 | }; | 474 | }; | ... | ... |
| ... | @@ -239,6 +239,17 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] { | ... | @@ -239,6 +239,17 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] { |
| 239 | } | 239 | } |
| 240 | } | 240 | } |
| 241 | 241 | ||
| 242 | +input.outline { | ||
| 243 | + border: 0; | ||
| 244 | + border-bottom: 2px solid #DDD; | ||
| 245 | + border-radius: 0; | ||
| 246 | + &:focus, &:active { | ||
| 247 | + border: 0; | ||
| 248 | + border-bottom: 2px solid #AAA; | ||
| 249 | + outline: 0; | ||
| 250 | + } | ||
| 251 | +} | ||
| 252 | + | ||
| 242 | #login-form label[for="remember"] { | 253 | #login-form label[for="remember"] { |
| 243 | margin: 0; | 254 | margin: 0; |
| 244 | } | 255 | } | ... | ... |
| ... | @@ -208,11 +208,70 @@ $btt-size: 40px; | ... | @@ -208,11 +208,70 @@ $btt-size: 40px; |
| 208 | background-color: #FFF; | 208 | background-color: #FFF; |
| 209 | border: 1px solid #BBB; | 209 | border: 1px solid #BBB; |
| 210 | border-radius: 3px; | 210 | border-radius: 3px; |
| 211 | - padding: $-l; | ||
| 212 | position: fixed; | 211 | position: fixed; |
| 213 | right: $-xl*2; | 212 | right: $-xl*2; |
| 214 | top: 100px; | 213 | top: 100px; |
| 215 | z-index: 99; | 214 | z-index: 99; |
| 216 | height: 800px; | 215 | height: 800px; |
| 216 | + width: 480px; | ||
| 217 | overflow-y: scroll; | 217 | overflow-y: scroll; |
| 218 | + display: flex; | ||
| 219 | + align-items: stretch; | ||
| 220 | + flex-direction: row; | ||
| 221 | + > div { | ||
| 222 | + flex: 1; | ||
| 223 | + position: relative; | ||
| 224 | + } | ||
| 225 | + .tabs { | ||
| 226 | + display: block; | ||
| 227 | + border-right: 1px solid #DDD; | ||
| 228 | + width: 54px; | ||
| 229 | + flex: 0; | ||
| 230 | + } | ||
| 231 | + .tabs i { | ||
| 232 | + padding: 0; | ||
| 233 | + margin: 0; | ||
| 234 | + } | ||
| 235 | + .tabs [tab-button] { | ||
| 236 | + display: block; | ||
| 237 | + cursor: pointer; | ||
| 238 | + color: #666; | ||
| 239 | + padding: $-m; | ||
| 240 | + border-bottom: 1px solid rgba(255, 255, 255, 0.3); | ||
| 241 | + &.active { | ||
| 242 | + color: #444; | ||
| 243 | + background-color: rgba(0, 0, 0, 0.1); | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | + div[tab-content] .padded { | ||
| 247 | + padding: 0 $-m; | ||
| 248 | + } | ||
| 249 | + h4 { | ||
| 250 | + font-size: 24px; | ||
| 251 | + margin: $-m 0 0 0; | ||
| 252 | + padding: 0 $-m; | ||
| 253 | + } | ||
| 254 | + .tags input { | ||
| 255 | + max-width: 100%; | ||
| 256 | + width: 100%; | ||
| 257 | + min-width: 50px; | ||
| 258 | + } | ||
| 259 | + .tags td { | ||
| 260 | + padding-right: $-s; | ||
| 261 | + padding-top: $-s; | ||
| 262 | + } | ||
| 263 | + button.pos { | ||
| 264 | + position: absolute; | ||
| 265 | + bottom: 0; | ||
| 266 | + display: block; | ||
| 267 | + width: 100%; | ||
| 268 | + padding: $-s; | ||
| 269 | + border: 0; | ||
| 270 | + margin: 0; | ||
| 271 | + box-shadow: none; | ||
| 272 | + border-radius: 0; | ||
| 273 | + &:hover{ | ||
| 274 | + box-shadow: none; | ||
| 275 | + } | ||
| 276 | + } | ||
| 218 | } | 277 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -16,17 +16,7 @@ | ... | @@ -16,17 +16,7 @@ |
| 16 | @include('pages/form', ['model' => $page]) | 16 | @include('pages/form', ['model' => $page]) |
| 17 | </form> | 17 | </form> |
| 18 | 18 | ||
| 19 | - <div class="floating-toolbox" ng-controller="PageAttributeController" page-id="{{ $page->id or 0 }}"> | 19 | + @include('pages/form-toolbox') |
| 20 | - <form ng-submit="saveAttributes()"> | ||
| 21 | - <table> | ||
| 22 | - <tr ng-repeat="attribute in attributes"> | ||
| 23 | - <td><input type="text" ng-model="attribute.name" ng-change="attributeChange(attribute)" ng-blur="attributeBlur(attribute)" placeholder="Attribute Name"></td> | ||
| 24 | - <td><input type="text" ng-model="attribute.value" ng-change="attributeChange(attribute)" ng-blur="attributeBlur(attribute)" placeholder="Value"></td> | ||
| 25 | - </tr> | ||
| 26 | - </table> | ||
| 27 | - <button class="button pos" type="submit">Save attributes</button> | ||
| 28 | - </form> | ||
| 29 | - </div> | ||
| 30 | 20 | ||
| 31 | </div> | 21 | </div> |
| 32 | @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id]) | 22 | @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id]) | ... | ... |
resources/views/pages/form-toolbox.blade.php
0 → 100644
| 1 | +<div class="floating-toolbox"> | ||
| 2 | + <div class="tabs primary-background-light"> | ||
| 3 | + <span tab-button class="active"><i class="zmdi zmdi-tag"></i></span> | ||
| 4 | + <span tab-button><i class="zmdi zmdi-wrench"></i></span> | ||
| 5 | + </div> | ||
| 6 | + <div tab-content ng-controller="PageTagController" page-id="{{ $page->id or 0 }}"> | ||
| 7 | + <form ng-submit="saveTags()" > | ||
| 8 | + <h4>Page Tags</h4> | ||
| 9 | + <div class="padded tags"> | ||
| 10 | + <table class="no-style" style="width: 100%;"> | ||
| 11 | + <tr ng-repeat="tag in tags"> | ||
| 12 | + <td><input class="outline" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td> | ||
| 13 | + <td><input class="outline" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td> | ||
| 14 | + </tr> | ||
| 15 | + </table> | ||
| 16 | + </div> | ||
| 17 | + <button class="button pos" type="submit">Save Tags</button> | ||
| 18 | + </form> | ||
| 19 | + </div> | ||
| 20 | +</div> | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | @if(Setting::get('app-color')) | 1 | @if(Setting::get('app-color')) |
| 2 | <style> | 2 | <style> |
| 3 | - header, #back-to-top { | 3 | + header, #back-to-top, .primary-background { |
| 4 | background-color: {{ Setting::get('app-color') }}; | 4 | background-color: {{ Setting::get('app-color') }}; |
| 5 | } | 5 | } |
| 6 | - .faded-small { | 6 | + .faded-small, .primary-background-light { |
| 7 | background-color: {{ Setting::get('app-color-light') }}; | 7 | background-color: {{ Setting::get('app-color-light') }}; |
| 8 | } | 8 | } |
| 9 | .button-base, .button, input[type="button"], input[type="submit"] { | 9 | .button-base, .button, input[type="button"], input[type="submit"] { | ... | ... |
| 1 | <?php namespace Entity; | 1 | <?php namespace Entity; |
| 2 | 2 | ||
| 3 | -use BookStack\Attribute; | 3 | +use BookStack\Tag; |
| 4 | use BookStack\Page; | 4 | use BookStack\Page; |
| 5 | use BookStack\Services\PermissionService; | 5 | use BookStack\Services\PermissionService; |
| 6 | 6 | ||
| 7 | -class AttributeTests extends \TestCase | 7 | +class TagTests extends \TestCase |
| 8 | { | 8 | { |
| 9 | 9 | ||
| 10 | - protected $defaultAttrCount = 20; | 10 | + protected $defaultTagCount = 20; |
| 11 | 11 | ||
| 12 | /** | 12 | /** |
| 13 | - * Get an instance of a page that has many attributes. | 13 | + * Get an instance of a page that has many tags. |
| 14 | - * @param Attribute[]|bool $attributes | 14 | + * @param Tag[]|bool $tags |
| 15 | * @return mixed | 15 | * @return mixed |
| 16 | */ | 16 | */ |
| 17 | - protected function getPageWithAttributes($attributes = false) | 17 | + protected function getPageWithTags($tags = false) |
| 18 | { | 18 | { |
| 19 | $page = Page::first(); | 19 | $page = Page::first(); |
| 20 | 20 | ||
| 21 | - if (!$attributes) { | 21 | + if (!$tags) { |
| 22 | - $attributes = factory(Attribute::class, $this->defaultAttrCount)->make(); | 22 | + $tags = factory(Tag::class, $this->defaultTagCount)->make(); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | - $page->attributes()->saveMany($attributes); | 25 | + $page->tags()->saveMany($tags); |
| 26 | return $page; | 26 | return $page; |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | - public function test_get_page_attributes() | 29 | + public function test_get_page_tags() |
| 30 | { | 30 | { |
| 31 | - $page = $this->getPageWithAttributes(); | 31 | + $page = $this->getPageWithTags(); |
| 32 | 32 | ||
| 33 | - // Add some other attributes to check they don't interfere | 33 | + // Add some other tags to check they don't interfere |
| 34 | - factory(Attribute::class, $this->defaultAttrCount)->create(); | 34 | + factory(Tag::class, $this->defaultTagCount)->create(); |
| 35 | 35 | ||
| 36 | - $this->asAdmin()->get("/ajax/attributes/get/page/" . $page->id) | 36 | + $this->asAdmin()->get("/ajax/tags/get/page/" . $page->id) |
| 37 | ->shouldReturnJson(); | 37 | ->shouldReturnJson(); |
| 38 | 38 | ||
| 39 | $json = json_decode($this->response->getContent()); | 39 | $json = json_decode($this->response->getContent()); |
| 40 | - $this->assertTrue(count($json) === $this->defaultAttrCount, "Returned JSON item count is not as expected"); | 40 | + $this->assertTrue(count($json) === $this->defaultTagCount, "Returned JSON item count is not as expected"); |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | - public function test_attribute_name_suggestions() | 43 | + public function test_tag_name_suggestions() |
| 44 | { | 44 | { |
| 45 | - // Create some attributes with similar names to test with | 45 | + // Create some tags with similar names to test with |
| 46 | $attrs = collect(); | 46 | $attrs = collect(); |
| 47 | - $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'country'])); | 47 | + $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'country'])); |
| 48 | - $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'color'])); | 48 | + $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'color'])); |
| 49 | - $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'city'])); | 49 | + $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'city'])); |
| 50 | - $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'county'])); | 50 | + $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'county'])); |
| 51 | - $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'planet'])); | 51 | + $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'planet'])); |
| 52 | - $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'plans'])); | 52 | + $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'plans'])); |
| 53 | - $page = $this->getPageWithAttributes($attrs); | 53 | + $page = $this->getPageWithTags($attrs); |
| 54 | - | 54 | + |
| 55 | - $this->asAdmin()->get('/ajax/attributes/suggest?search=dog')->seeJsonEquals([]); | 55 | + $this->asAdmin()->get('/ajax/tags/suggest?search=dog')->seeJsonEquals([]); |
| 56 | - $this->get('/ajax/attributes/suggest?search=co')->seeJsonEquals(['color', 'country', 'county']); | 56 | + $this->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country', 'county']); |
| 57 | - $this->get('/ajax/attributes/suggest?search=cou')->seeJsonEquals(['country', 'county']); | 57 | + $this->get('/ajax/tags/suggest?search=cou')->seeJsonEquals(['country', 'county']); |
| 58 | - $this->get('/ajax/attributes/suggest?search=pla')->seeJsonEquals(['planet', 'plans']); | 58 | + $this->get('/ajax/tags/suggest?search=pla')->seeJsonEquals(['planet', 'plans']); |
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | - public function test_entity_permissions_effect_attribute_suggestions() | 61 | + public function test_entity_permissions_effect_tag_suggestions() |
| 62 | { | 62 | { |
| 63 | $permissionService = $this->app->make(PermissionService::class); | 63 | $permissionService = $this->app->make(PermissionService::class); |
| 64 | 64 | ||
| 65 | - // Create some attributes with similar names to test with and save to a page | 65 | + // Create some tags with similar names to test with and save to a page |
| 66 | $attrs = collect(); | 66 | $attrs = collect(); |
| 67 | - $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'country'])); | 67 | + $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'country'])); |
| 68 | - $attrs = $attrs->merge(factory(Attribute::class, 5)->make(['name' => 'color'])); | 68 | + $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'color'])); |
| 69 | - $page = $this->getPageWithAttributes($attrs); | 69 | + $page = $this->getPageWithTags($attrs); |
| 70 | 70 | ||
| 71 | - $this->asAdmin()->get('/ajax/attributes/suggest?search=co')->seeJsonEquals(['color', 'country']); | 71 | + $this->asAdmin()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); |
| 72 | - $this->asEditor()->get('/ajax/attributes/suggest?search=co')->seeJsonEquals(['color', 'country']); | 72 | + $this->asEditor()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); |
| 73 | 73 | ||
| 74 | // Set restricted permission the page | 74 | // Set restricted permission the page |
| 75 | $page->restricted = true; | 75 | $page->restricted = true; |
| 76 | $page->save(); | 76 | $page->save(); |
| 77 | $permissionService->buildJointPermissionsForEntity($page); | 77 | $permissionService->buildJointPermissionsForEntity($page); |
| 78 | 78 | ||
| 79 | - $this->asAdmin()->get('/ajax/attributes/suggest?search=co')->seeJsonEquals(['color', 'country']); | 79 | + $this->asAdmin()->get('/ajax/tags/suggest?search=co')->seeJsonEquals(['color', 'country']); |
| 80 | - $this->asEditor()->get('/ajax/attributes/suggest?search=co')->seeJsonEquals([]); | 80 | + $this->asEditor()->get('/ajax/tags/suggest?search=co')->seeJsonEquals([]); |
| 81 | } | 81 | } |
| 82 | 82 | ||
| 83 | - public function test_entity_attribute_updating() | 83 | + public function test_entity_tag_updating() |
| 84 | { | 84 | { |
| 85 | - $page = $this->getPageWithAttributes(); | 85 | + $page = $this->getPageWithTags(); |
| 86 | 86 | ||
| 87 | $testJsonData = [ | 87 | $testJsonData = [ |
| 88 | ['name' => 'color', 'value' => 'red'], | 88 | ['name' => 'color', 'value' => 'red'], |
| ... | @@ -98,23 +98,23 @@ class AttributeTests extends \TestCase | ... | @@ -98,23 +98,23 @@ class AttributeTests extends \TestCase |
| 98 | ]; | 98 | ]; |
| 99 | 99 | ||
| 100 | // Do update request | 100 | // Do update request |
| 101 | - $this->asAdmin()->json("POST", "/ajax/attributes/update/page/" . $page->id, ['attributes' => $testJsonData]); | 101 | + $this->asAdmin()->json("POST", "/ajax/tags/update/page/" . $page->id, ['tags' => $testJsonData]); |
| 102 | $updateData = json_decode($this->response->getContent()); | 102 | $updateData = json_decode($this->response->getContent()); |
| 103 | // Check data is correct | 103 | // Check data is correct |
| 104 | $testDataCorrect = true; | 104 | $testDataCorrect = true; |
| 105 | - foreach ($updateData->attributes as $data) { | 105 | + foreach ($updateData->tags as $data) { |
| 106 | $testItem = ['name' => $data->name, 'value' => $data->value]; | 106 | $testItem = ['name' => $data->name, 'value' => $data->value]; |
| 107 | if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false; | 107 | if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false; |
| 108 | } | 108 | } |
| 109 | $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s"; | 109 | $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s"; |
| 110 | $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($updateData))); | 110 | $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($updateData))); |
| 111 | - $this->assertTrue(isset($updateData->message), "No message returned in attribute update response"); | 111 | + $this->assertTrue(isset($updateData->message), "No message returned in tag update response"); |
| 112 | 112 | ||
| 113 | // Do get request | 113 | // Do get request |
| 114 | - $this->asAdmin()->get("/ajax/attributes/get/page/" . $page->id); | 114 | + $this->asAdmin()->get("/ajax/tags/get/page/" . $page->id); |
| 115 | $getResponseData = json_decode($this->response->getContent()); | 115 | $getResponseData = json_decode($this->response->getContent()); |
| 116 | // Check counts | 116 | // Check counts |
| 117 | - $this->assertTrue(count($getResponseData) === count($testJsonData), "The received attribute count is incorrect"); | 117 | + $this->assertTrue(count($getResponseData) === count($testJsonData), "The received tag count is incorrect"); |
| 118 | // Check data is correct | 118 | // Check data is correct |
| 119 | $testDataCorrect = true; | 119 | $testDataCorrect = true; |
| 120 | foreach ($getResponseData as $data) { | 120 | foreach ($getResponseData as $data) { | ... | ... |
-
Please register or sign in to post a comment