diff --git a/app/Entity.php b/app/Entity.php index 79ff9ff38..7347ddcd6 100644 --- a/app/Entity.php +++ b/app/Entity.php @@ -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 index 6e6913722..000000000 --- a/app/Http/Controllers/AttributeController.php +++ /dev/null @@ -1,64 +0,0 @@ -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 index 000000000..e236986b6 --- /dev/null +++ b/app/Http/Controllers/TagController.php @@ -0,0 +1,64 @@ +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); + } + + +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 7c6911b2e..629f61ba9 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -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 diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index ef50b7181..938c0f5ae 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -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); diff --git a/app/Repos/AttributeRepo.php b/app/Repos/TagRepo.php similarity index 60% rename from app/Repos/AttributeRepo.php rename to app/Repos/TagRepo.php index fb742b2d8..5d3670e6f 100644 --- a/app/Repos/AttributeRepo.php +++ b/app/Repos/TagRepo.php @@ -1,29 +1,29 @@ 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 diff --git a/app/Attribute.php b/app/Tag.php similarity index 78% rename from app/Attribute.php rename to app/Tag.php index 3fe201a42..2eeb0b1f9 100644 --- a/app/Attribute.php +++ b/app/Tag.php @@ -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() diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index a1a6a92a0..3820d5b59 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -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) diff --git a/database/migrations/2016_05_06_185215_create_attributes_table.php b/database/migrations/2016_05_06_185215_create_tags_table.php 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 b6412037c..55eed6060 100644 --- a/database/migrations/2016_05_06_185215_create_attributes_table.php +++ b/database/migrations/2016_05_06_185215_create_tags_table.php @@ -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'); } } diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js index e73efaac9..bec96f319 100644 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@ -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); }) }; diff --git a/resources/assets/sass/_forms.scss b/resources/assets/sass/_forms.scss index 482cf54bd..4b50f6022 100644 --- a/resources/assets/sass/_forms.scss +++ b/resources/assets/sass/_forms.scss @@ -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; } diff --git a/resources/assets/sass/_tables.scss b/resources/assets/sass/_tables.scss index e6ec76b38..b23db436a 100644 --- a/resources/assets/sass/_tables.scss +++ b/resources/assets/sass/_tables.scss @@ -26,6 +26,13 @@ table { } } +table.no-style { + td { + border: 0; + padding: 0; + } +} + table.list-table { margin: 0 -$-xs; td { diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index 4b6b1cc46..b48b7dd4e 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -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 diff --git a/resources/views/pages/edit.blade.php b/resources/views/pages/edit.blade.php index a536ad23e..d5cb1d3e3 100644 --- a/resources/views/pages/edit.blade.php +++ b/resources/views/pages/edit.blade.php @@ -16,17 +16,7 @@ @include('pages/form', ['model' => $page]) -
-
- - - - - -
- -
-
+ @include('pages/form-toolbox') @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 index 000000000..d223f17d5 --- /dev/null +++ b/resources/views/pages/form-toolbox.blade.php @@ -0,0 +1,20 @@ +
+
+ + +
+
+
+

Page Tags

+
+ + + + + +
+
+ +
+
+
\ No newline at end of file diff --git a/resources/views/partials/custom-styles.blade.php b/resources/views/partials/custom-styles.blade.php index de324d284..c5937a2b1 100644 --- a/resources/views/partials/custom-styles.blade.php +++ b/resources/views/partials/custom-styles.blade.php @@ -1,9 +1,9 @@ @if(Setting::get('app-color'))