1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2024-11-23 11:22:33 +01:00

Started the page attributes interface

This commit is contained in:
Dan Brown 2016-05-12 23:12:05 +01:00
parent fcfb9470c9
commit 1fa079b466
11 changed files with 152 additions and 34 deletions

View File

@ -6,7 +6,7 @@
*/
class Attribute extends Model
{
protected $fillable = ['name', 'value'];
protected $fillable = ['name', 'value', 'order'];
/**
* Get the entity that this attribute belongs to

View File

@ -38,18 +38,15 @@ class AttributeController extends Controller
*/
public function updateForEntity($entityType, $entityId, Request $request)
{
$this->validate($request, [
'attributes.*.name' => 'required|min:3|max:250',
'attributes.*.value' => 'max:250'
]);
$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);
return response()->json([
'attributes' => $attributes,
'message' => 'Attributes successfully updated'
]);
}
/**

View File

@ -72,7 +72,7 @@ class PageController extends Controller
$this->checkOwnablePermission('page-create', $book);
$this->setPageTitle('Edit Page Draft');
return view('pages/create', ['draft' => $draft, 'book' => $book]);
return view('pages/edit', ['page' => $draft, 'book' => $book, 'isDraft' => true]);
}
/**

View File

@ -80,6 +80,7 @@ class AttributeRepo
$entity->attributes()->delete();
$newAttributes = [];
foreach ($attributes as $attribute) {
if (trim($attribute['name']) === '') continue;
$newAttributes[] = $this->newInstanceFromInput($attribute);
}

View File

@ -18,10 +18,12 @@ class CreateAttributesTable extends Migration
$table->string('entity_type', 100);
$table->string('name');
$table->string('value');
$table->integer('order');
$table->timestamps();
$table->index('name');
$table->index('value');
$table->index('order');
$table->index(['entity_id', 'entity_type']);
});
}

View File

@ -400,4 +400,96 @@ module.exports = function (ngApp, events) {
}]);
};
ngApp.controller('PageAttributeController', ['$scope', '$http', '$attrs',
function ($scope, $http, $attrs) {
const pageId = Number($attrs.pageId);
$scope.attributes = [];
/**
* Push an empty attribute to the end of the scope attributes.
*/
function addEmptyAttribute() {
$scope.attributes.push({
name: '',
value: ''
});
}
/**
* Get all attributes for the current book and add into scope.
*/
function getAttributes() {
$http.get('/ajax/attributes/get/page/' + pageId).then((responseData) => {
$scope.attributes = responseData.data;
addEmptyAttribute();
});
}
getAttributes();
/**
* Set the order property on all attributes.
*/
function setAttributeOrder() {
for (let i = 0; i < $scope.attributes.length; i++) {
$scope.attributes[i].order = i;
}
}
/**
* When an attribute changes check if another empty editable
* field needs to be added onto the end.
* @param attribute
*/
$scope.attributeChange = function(attribute) {
let cPos = $scope.attributes.indexOf(attribute);
if (cPos !== $scope.attributes.length-1) return;
if (attribute.name !== '' || attribute.value !== '') {
addEmptyAttribute();
}
};
/**
* When an attribute field loses focus check the attribute to see if its
* empty and therefore could be removed from the list.
* @param attribute
*/
$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.saveAttributes = function() {
setAttributeOrder();
let postData = {attributes: $scope.attributes};
$http.post('/ajax/attributes/update/page/' + pageId, postData).then((responseData) => {
$scope.attributes = responseData.data.attributes;
addEmptyAttribute();
events.emit('success', responseData.data.message);
})
};
}]);
};

View File

@ -201,4 +201,18 @@ $btt-size: 40px;
background-color: $negative;
color: #EEE;
}
}
// Attribute form
.floating-toolbox {
background-color: #FFF;
border: 1px solid #BBB;
border-radius: 3px;
padding: $-l;
position: fixed;
right: $-xl*2;
top: 100px;
z-index: 99;
height: 800px;
overflow-y: scroll;
}

View File

@ -1,17 +0,0 @@
@extends('base')
@section('head')
<script src="/libs/tinymce/tinymce.min.js?ver=4.3.7"></script>
@stop
@section('body-class', 'flexbox')
@section('content')
<div class="flex-fill flex">
<form action="{{$book->getUrl() . '/page/' . $draft->id}}" method="POST" class="flex flex-fill">
@include('pages/form', ['model' => $draft])
</form>
</div>
@include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $draft->id])
@stop

View File

@ -10,9 +10,24 @@
<div class="flex-fill flex">
<form action="{{$page->getUrl()}}" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill">
<input type="hidden" name="_method" value="PUT">
@if(!isset($isDraft))
<input type="hidden" name="_method" value="PUT">
@endif
@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>
</div>
@include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])

View File

@ -41,6 +41,7 @@
@include('form/text', ['name' => 'name', 'placeholder' => 'Page Title'])
</div>
</div>
<div class="edit-area flex-fill flex">
@if(setting('app-editor') === 'wysiwyg')
<textarea id="html-editor" tinymce="editorOptions" mce-change="editorChange" mce-model="editContent" name="html" rows="5"

View File

@ -97,19 +97,32 @@ class AttributeTests extends \TestCase
['name' => 'country', 'value' => 'England'],
];
// Do update request
$this->asAdmin()->json("POST", "/ajax/attributes/update/page/" . $page->id, ['attributes' => $testJsonData]);
$this->asAdmin()->get("/ajax/attributes/get/page/" . $page->id);
$jsonData = json_decode($this->response->getContent());
// Check counts
$this->assertTrue(count($jsonData) === count($testJsonData), "The received attribute count is incorrect");
$updateData = json_decode($this->response->getContent());
// Check data is correct
$testDataCorrect = true;
foreach ($jsonData as $data) {
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($jsonData)));
$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)));
}
}