Skip to content

Commit 3045840

Browse files
committed
Page Attachments - Improved UI, Now initially complete
Closes #62
1 parent 9122023 commit 3045840

File tree

11 files changed

+222
-69
lines changed

11 files changed

+222
-69
lines changed

app/File.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function page()
3030
*/
3131
public function getUrl()
3232
{
33-
return '/files/' . $this->id;
33+
return baseUrl('/files/' . $this->id);
3434
}
3535

3636
}

app/Http/Controllers/Controller.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
namespace BookStack\Http\Controllers;
44

55
use BookStack\Ownable;
6-
use HttpRequestException;
76
use Illuminate\Foundation\Bus\DispatchesJobs;
87
use Illuminate\Http\Exception\HttpResponseException;
8+
use Illuminate\Http\Request;
99
use Illuminate\Routing\Controller as BaseController;
1010
use Illuminate\Foundation\Validation\ValidatesRequests;
11-
use Illuminate\Support\Facades\Auth;
12-
use Illuminate\Support\Facades\Session;
1311
use BookStack\User;
1412

1513
abstract class Controller extends BaseController
@@ -130,4 +128,22 @@ protected function jsonError($messageText = "", $statusCode = 500)
130128
return response()->json(['message' => $messageText], $statusCode);
131129
}
132130

131+
/**
132+
* Create the response for when a request fails validation.
133+
*
134+
* @param \Illuminate\Http\Request $request
135+
* @param array $errors
136+
* @return \Symfony\Component\HttpFoundation\Response
137+
*/
138+
protected function buildFailedValidationResponse(Request $request, array $errors)
139+
{
140+
if ($request->expectsJson()) {
141+
return response()->json(['validation' => $errors], 422);
142+
}
143+
144+
return redirect()->to($this->getRedirectUrl())
145+
->withInput($request->input())
146+
->withErrors($errors, $this->errorBag());
147+
}
148+
133149
}

app/Http/Controllers/FileController.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ public function update($fileId, Request $request)
101101
{
102102
$this->validate($request, [
103103
'uploaded_to' => 'required|integer|exists:pages,id',
104-
'name' => 'string|max:255',
105-
'link' => 'url'
104+
'name' => 'required|string|min:1|max:255',
105+
'link' => 'url|min:1|max:255'
106106
]);
107107

108108
$pageId = $request->get('uploaded_to');
@@ -129,8 +129,8 @@ public function attachLink(Request $request)
129129
{
130130
$this->validate($request, [
131131
'uploaded_to' => 'required|integer|exists:pages,id',
132-
'name' => 'string|max:255',
133-
'link' => 'url|max:255'
132+
'name' => 'required|string|min:1|max:255',
133+
'link' => 'required|url|min:1|max:255'
134134
]);
135135

136136
$pageId = $request->get('uploaded_to');

resources/assets/js/controllers.js

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,10 @@ module.exports = function (ngApp, events) {
538538
$scope.files = [];
539539
$scope.editFile = false;
540540
$scope.file = getCleanFile();
541+
$scope.errors = {
542+
link: {},
543+
edit: {}
544+
};
541545

542546
function getCleanFile() {
543547
return {
@@ -567,7 +571,7 @@ module.exports = function (ngApp, events) {
567571
currentOrder = newOrder;
568572
$http.put(`/files/sort/page/${pageId}`, {files: $scope.files}).then(resp => {
569573
events.emit('success', resp.data.message);
570-
}, checkError);
574+
}, checkError('sort'));
571575
}
572576

573577
/**
@@ -587,7 +591,7 @@ module.exports = function (ngApp, events) {
587591
$http.get(url).then(resp => {
588592
$scope.files = resp.data;
589593
currentOrder = resp.data.map(file => {return file.id}).join(':');
590-
}, checkError);
594+
}, checkError('get'));
591595
}
592596
getFiles();
593597

@@ -599,7 +603,7 @@ module.exports = function (ngApp, events) {
599603
*/
600604
$scope.uploadSuccess = function (file, data) {
601605
$scope.$apply(() => {
602-
$scope.files.unshift(data);
606+
$scope.files.push(data);
603607
});
604608
events.emit('success', 'File uploaded');
605609
};
@@ -612,10 +616,10 @@ module.exports = function (ngApp, events) {
612616
$scope.uploadSuccessUpdate = function (file, data) {
613617
$scope.$apply(() => {
614618
let search = filesIndexOf(data);
615-
if (search !== -1) $scope.files[search] = file;
619+
if (search !== -1) $scope.files[search] = data;
616620

617621
if ($scope.editFile) {
618-
$scope.editFile = data;
622+
$scope.editFile = angular.copy(data);
619623
data.link = '';
620624
}
621625
});
@@ -627,10 +631,14 @@ module.exports = function (ngApp, events) {
627631
* @param file
628632
*/
629633
$scope.deleteFile = function(file) {
634+
if (!file.deleting) {
635+
file.deleting = true;
636+
return;
637+
}
630638
$http.delete(`/files/${file.id}`).then(resp => {
631639
events.emit('success', resp.data.message);
632640
$scope.files.splice($scope.files.indexOf(file), 1);
633-
}, checkError);
641+
}, checkError('delete'));
634642
};
635643

636644
/**
@@ -641,19 +649,20 @@ module.exports = function (ngApp, events) {
641649
$scope.attachLinkSubmit = function(file) {
642650
file.uploaded_to = pageId;
643651
$http.post('/files/link', file).then(resp => {
644-
$scope.files.unshift(resp.data);
652+
$scope.files.push(resp.data);
645653
events.emit('success', 'Link attached');
646654
$scope.file = getCleanFile();
647-
}, checkError);
655+
}, checkError('link'));
648656
};
649657

650658
/**
651659
* Start the edit mode for a file.
652660
* @param fileId
653661
*/
654662
$scope.startEdit = function(file) {
663+
console.log(file);
655664
$scope.editFile = angular.copy(file);
656-
if (!file.external) $scope.editFile.link = '';
665+
$scope.editFile.link = (file.external) ? file.path : '';
657666
};
658667

659668
/**
@@ -670,16 +679,23 @@ module.exports = function (ngApp, events) {
670679
$scope.updateFile = function(file) {
671680
$http.put(`/files/${file.id}`, file).then(resp => {
672681
let search = filesIndexOf(resp.data);
673-
if (search !== -1) $scope.files[search] = file;
682+
if (search !== -1) $scope.files[search] = resp.data;
674683

675684
if ($scope.editFile && !file.external) {
676685
$scope.editFile.link = '';
677686
}
678687
$scope.editFile = false;
679688
events.emit('success', 'Attachment details updated');
680-
});
689+
}, checkError('edit'));
681690
};
682691

692+
/**
693+
* Get the url of a file.
694+
*/
695+
$scope.getFileUrl = function(file) {
696+
return window.baseUrl('/files/' + file.id);
697+
}
698+
683699
/**
684700
* Search the local files via another file object.
685701
* Used to search via object copies.
@@ -697,9 +713,16 @@ module.exports = function (ngApp, events) {
697713
* Check for an error response in a ajax request.
698714
* @param response
699715
*/
700-
function checkError(response) {
701-
if (typeof response.data !== 'undefined' && typeof response.data.error !== 'undefined') {
702-
events.emit('error', response.data.error);
716+
function checkError(errorGroupName) {
717+
$scope.errors[errorGroupName] = {};
718+
return function(response) {
719+
if (typeof response.data !== 'undefined' && typeof response.data.error !== 'undefined') {
720+
events.emit('error', response.data.error);
721+
}
722+
if (typeof response.data !== 'undefined' && typeof response.data.validation !== 'undefined') {
723+
$scope.errors[errorGroupName] = response.data.validation;
724+
console.log($scope.errors[errorGroupName])
725+
}
703726
}
704727
}
705728

resources/assets/js/directives.js

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,59 @@ module.exports = function (ngApp, events) {
3333
};
3434
});
3535

36+
/**
37+
* Common tab controls using simple jQuery functions.
38+
*/
39+
ngApp.directive('tabContainer', function() {
40+
return {
41+
restrict: 'A',
42+
link: function (scope, element, attrs) {
43+
const $content = element.find('[tab-content]');
44+
const $buttons = element.find('[tab-button]');
45+
46+
if (attrs.tabContainer) {
47+
let initial = attrs.tabContainer;
48+
$buttons.filter(`[tab-button="${initial}"]`).addClass('selected');
49+
$content.hide().filter(`[tab-content="${initial}"]`).show();
50+
} else {
51+
$content.hide().first().show();
52+
$buttons.first().addClass('selected');
53+
}
54+
55+
$buttons.click(function() {
56+
let clickedTab = $(this);
57+
$buttons.removeClass('selected');
58+
$content.hide();
59+
let name = clickedTab.addClass('selected').attr('tab-button');
60+
$content.filter(`[tab-content="${name}"]`).show();
61+
});
62+
}
63+
};
64+
});
65+
66+
/**
67+
* Sub form component to allow inner-form sections to act like thier own forms.
68+
*/
69+
ngApp.directive('subForm', function() {
70+
return {
71+
restrict: 'A',
72+
link: function (scope, element, attrs) {
73+
element.on('keypress', e => {
74+
if (e.keyCode === 13) {
75+
submitEvent(e);
76+
}
77+
});
78+
79+
element.find('button[type="submit"]').click(submitEvent);
80+
81+
function submitEvent(e) {
82+
e.preventDefault()
83+
if (attrs.subForm) scope.$eval(attrs.subForm);
84+
}
85+
}
86+
};
87+
});
88+
3689

3790
/**
3891
* Image Picker
@@ -489,8 +542,8 @@ module.exports = function (ngApp, events) {
489542
link: function (scope, elem, attrs) {
490543

491544
// Get common elements
492-
const $buttons = elem.find('[tab-button]');
493-
const $content = elem.find('[tab-content]');
545+
const $buttons = elem.find('[toolbox-tab-button]');
546+
const $content = elem.find('[toolbox-tab-content]');
494547
const $toggle = elem.find('[toolbox-toggle]');
495548

496549
// Handle toolbox toggle click
@@ -502,17 +555,17 @@ module.exports = function (ngApp, events) {
502555
function setActive(tabName, openToolbox) {
503556
$buttons.removeClass('active');
504557
$content.hide();
505-
$buttons.filter(`[tab-button="${tabName}"]`).addClass('active');
506-
$content.filter(`[tab-content="${tabName}"]`).show();
558+
$buttons.filter(`[toolbox-tab-button="${tabName}"]`).addClass('active');
559+
$content.filter(`[toolbox-tab-content="${tabName}"]`).show();
507560
if (openToolbox) elem.addClass('open');
508561
}
509562

510563
// Set the first tab content active on load
511-
setActive($content.first().attr('tab-content'), false);
564+
setActive($content.first().attr('toolbox-tab-content'), false);
512565

513566
// Handle tab button click
514567
$buttons.click(function (e) {
515-
let name = $(this).attr('tab-button');
568+
let name = $(this).attr('toolbox-tab-button');
516569
setActive(name, true);
517570
});
518571
}

resources/assets/sass/_components.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,3 +452,17 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
452452
border-right: 6px solid transparent;
453453
border-bottom: 6px solid $negative;
454454
}
455+
456+
457+
[tab-container] .nav-tabs {
458+
text-align: left;
459+
border-bottom: 1px solid #DDD;
460+
margin-bottom: $-m;
461+
.tab-item {
462+
padding: $-s;
463+
color: #666;
464+
&.selected {
465+
border-bottom-width: 3px;
466+
}
467+
}
468+
}

resources/assets/sass/_pages.scss

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@
150150
background-color: #FFF;
151151
border: 1px solid #DDD;
152152
right: $-xl*2;
153-
z-index: 99;
154153
width: 48px;
155154
overflow: hidden;
156155
align-items: stretch;
@@ -201,15 +200,15 @@
201200
color: #444;
202201
background-color: rgba(0, 0, 0, 0.1);
203202
}
204-
div[tab-content] {
203+
div[toolbox-tab-content] {
205204
padding-bottom: 45px;
206205
display: flex;
207206
flex: 1;
208207
flex-direction: column;
209208
min-height: 0px;
210209
overflow-y: scroll;
211210
}
212-
div[tab-content] .padded {
211+
div[toolbox-tab-content] .padded {
213212
flex: 1;
214213
padding-top: 0;
215214
}
@@ -241,7 +240,7 @@
241240
}
242241
}
243242

244-
[tab-content] {
243+
[toolbox-tab-content] {
245244
display: none;
246245
}
247246

resources/assets/sass/_tables.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,14 @@ table.list-table {
5151
vertical-align: middle;
5252
padding: $-xs;
5353
}
54+
}
55+
56+
table.file-table {
57+
@extend .no-style;
58+
td {
59+
padding: $-xs;
60+
}
61+
.ui-sortable-helper {
62+
display: table;
63+
}
5464
}

0 commit comments

Comments
 (0)