Dan Brown

converted image picker to blade-based component

Also updated some other JS translations
...@@ -117,7 +117,7 @@ class AttachmentController extends Controller ...@@ -117,7 +117,7 @@ class AttachmentController extends Controller
117 } 117 }
118 118
119 $attachment = $this->attachmentService->updateFile($attachment, $request->all()); 119 $attachment = $this->attachmentService->updateFile($attachment, $request->all());
120 - return $attachment; 120 + return $this->jsonSuccess($attachment, trans('entities.attachments_updated_success'));
121 } 121 }
122 122
123 /** 123 /**
......
...@@ -118,6 +118,17 @@ abstract class Controller extends BaseController ...@@ -118,6 +118,17 @@ abstract class Controller extends BaseController
118 } 118 }
119 119
120 /** 120 /**
121 + * Send a json respons with a message attached as a header.
122 + * @param $data
123 + * @param string $successMessage
124 + * @return $this
125 + */
126 + protected function jsonSuccess($data, $successMessage = "")
127 + {
128 + return response()->json($data)->header('message-success', $successMessage);
129 + }
130 +
131 + /**
121 * Send back a json error message. 132 * Send back a json error message.
122 * @param string $messageText 133 * @param string $messageText
123 * @param int $statusCode 134 * @param int $statusCode
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
6 return [ 6 return [
7 7
8 'app-name' => 'BookStack', 8 'app-name' => 'BookStack',
9 + 'app-logo' => '',
9 'app-name-header' => true, 10 'app-name-header' => true,
10 'app-editor' => 'wysiwyg', 11 'app-editor' => 'wysiwyg',
11 'app-color' => '#0288D1', 12 'app-color' => '#0288D1',
......
...@@ -576,7 +576,7 @@ export default function (ngApp, events) { ...@@ -576,7 +576,7 @@ export default function (ngApp, events) {
576 * Get files for the current page from the server. 576 * Get files for the current page from the server.
577 */ 577 */
578 function getFiles() { 578 function getFiles() {
579 - let url = window.baseUrl(`/attachments/get/page/${pageId}`) 579 + let url = window.baseUrl(`/attachments/get/page/${pageId}`);
580 $http.get(url).then(resp => { 580 $http.get(url).then(resp => {
581 $scope.files = resp.data; 581 $scope.files = resp.data;
582 currentOrder = resp.data.map(file => {return file.id}).join(':'); 582 currentOrder = resp.data.map(file => {return file.id}).join(':');
...@@ -672,7 +672,7 @@ export default function (ngApp, events) { ...@@ -672,7 +672,7 @@ export default function (ngApp, events) {
672 $scope.editFile.link = ''; 672 $scope.editFile.link = '';
673 } 673 }
674 $scope.editFile = false; 674 $scope.editFile = false;
675 - events.emit('success', 'Attachment details updated'); 675 + events.emit('success', resp.headers('message-success'));
676 }, checkError('edit')); 676 }, checkError('edit'));
677 }; 677 };
678 678
......
...@@ -35,7 +35,7 @@ export default function (ngApp, events) { ...@@ -35,7 +35,7 @@ export default function (ngApp, events) {
35 }); 35 });
36 36
37 /** 37 /**
38 - * Sub form component to allow inner-form sections to act like thier own forms. 38 + * Sub form component to allow inner-form sections to act like their own forms.
39 */ 39 */
40 ngApp.directive('subForm', function() { 40 ngApp.directive('subForm', function() {
41 return { 41 return {
...@@ -50,96 +50,13 @@ export default function (ngApp, events) { ...@@ -50,96 +50,13 @@ export default function (ngApp, events) {
50 element.find('button[type="submit"]').click(submitEvent); 50 element.find('button[type="submit"]').click(submitEvent);
51 51
52 function submitEvent(e) { 52 function submitEvent(e) {
53 - e.preventDefault() 53 + e.preventDefault();
54 if (attrs.subForm) scope.$eval(attrs.subForm); 54 if (attrs.subForm) scope.$eval(attrs.subForm);
55 } 55 }
56 } 56 }
57 }; 57 };
58 }); 58 });
59 59
60 -
61 - /**
62 - * Image Picker
63 - * Is a simple front-end interface that connects to an ImageManager if present.
64 - */
65 - ngApp.directive('imagePicker', ['$http', 'imageManagerService', function ($http, imageManagerService) {
66 - return {
67 - restrict: 'E',
68 - template: `
69 - <div class="image-picker">
70 - <div>
71 - <img ng-if="image && image !== 'none'" ng-src="{{image}}" ng-class="{{imageClass}}" alt="Image Preview">
72 - <img ng-if="image === '' && defaultImage" ng-src="{{defaultImage}}" ng-class="{{imageClass}}" alt="Image Preview">
73 - </div>
74 - <button class="button" type="button" ng-click="showImageManager()">Select Image</button>
75 - <br>
76 -
77 - <button class="text-button" ng-click="reset()" type="button">Reset</button>
78 - <span ng-show="showRemove" class="sep">|</span>
79 - <button ng-show="showRemove" class="text-button neg" ng-click="remove()" type="button">Remove</button>
80 -
81 - <input type="hidden" ng-attr-name="{{name}}" ng-attr-id="{{name}}" ng-attr-value="{{value}}">
82 - </div>
83 - `,
84 - scope: {
85 - name: '@',
86 - resizeHeight: '@',
87 - resizeWidth: '@',
88 - resizeCrop: '@',
89 - showRemove: '=',
90 - currentImage: '@',
91 - currentId: '@',
92 - defaultImage: '@',
93 - imageClass: '@'
94 - },
95 - link: function (scope, element, attrs) {
96 - let usingIds = typeof scope.currentId !== 'undefined' || scope.currentId === 'false';
97 - scope.image = scope.currentImage;
98 - scope.value = scope.currentImage || '';
99 - if (usingIds) scope.value = scope.currentId;
100 -
101 - function setImage(imageModel, imageUrl) {
102 - scope.image = imageUrl;
103 - scope.value = usingIds ? imageModel.id : imageUrl;
104 - }
105 -
106 - scope.reset = function () {
107 - setImage({id: 0}, scope.defaultImage);
108 - };
109 -
110 - scope.remove = function () {
111 - scope.image = 'none';
112 - scope.value = 'none';
113 - };
114 -
115 - scope.showImageManager = function () {
116 - imageManagerService.show((image) => {
117 - scope.updateImageFromModel(image);
118 - });
119 - };
120 -
121 - scope.updateImageFromModel = function (model) {
122 - let isResized = scope.resizeWidth && scope.resizeHeight;
123 -
124 - if (!isResized) {
125 - scope.$apply(() => {
126 - setImage(model, model.url);
127 - });
128 - return;
129 - }
130 -
131 - let cropped = scope.resizeCrop ? 'true' : 'false';
132 - let requestString = '/images/thumb/' + model.id + '/' + scope.resizeWidth + '/' + scope.resizeHeight + '/' + cropped;
133 - requestString = window.baseUrl(requestString);
134 - $http.get(requestString).then((response) => {
135 - setImage(model, response.data.url);
136 - });
137 - };
138 -
139 - }
140 - };
141 - }]);
142 -
143 /** 60 /**
144 * DropZone 61 * DropZone
145 * Used for uploading images 62 * Used for uploading images
...@@ -149,16 +66,17 @@ export default function (ngApp, events) { ...@@ -149,16 +66,17 @@ export default function (ngApp, events) {
149 restrict: 'E', 66 restrict: 'E',
150 template: ` 67 template: `
151 <div class="dropzone-container"> 68 <div class="dropzone-container">
152 - <div class="dz-message">Drop files or click here to upload</div> 69 + <div class="dz-message">{{message}}</div>
153 </div> 70 </div>
154 `, 71 `,
155 scope: { 72 scope: {
156 uploadUrl: '@', 73 uploadUrl: '@',
157 eventSuccess: '=', 74 eventSuccess: '=',
158 eventError: '=', 75 eventError: '=',
159 - uploadedTo: '@' 76 + uploadedTo: '@',
160 }, 77 },
161 link: function (scope, element, attrs) { 78 link: function (scope, element, attrs) {
79 + scope.message = attrs.message;
162 if (attrs.placeholder) element[0].querySelector('.dz-message').textContent = attrs.placeholder; 80 if (attrs.placeholder) element[0].querySelector('.dz-message').textContent = attrs.placeholder;
163 let dropZone = new DropZone(element[0].querySelector('.dropzone-container'), { 81 let dropZone = new DropZone(element[0].querySelector('.dropzone-container'), {
164 url: scope.uploadUrl, 82 url: scope.uploadUrl,
......
...@@ -161,6 +161,51 @@ $(function () { ...@@ -161,6 +161,51 @@ $(function () {
161 }); 161 });
162 } 162 }
163 163
164 + // Image pickers
165 + $('.image-picker').on('click', 'button', event => {
166 + let button = event.target;
167 + let picker = $(button).closest('.image-picker')[0];
168 + let action = button.getAttribute('data-action');
169 + let resize = picker.getAttribute('data-resize-height') && picker.getAttribute('data-resize-width');
170 + let usingIds = picker.getAttribute('data-current-id') !== '';
171 + let resizeCrop = picker.getAttribute('data-resize-crop') !== '';
172 + let imageElem = picker.querySelector('img');
173 + let input = picker.querySelector('input');
174 +
175 + function setImage(image) {
176 +
177 + if (image === 'none') {
178 + imageElem.src = picker.getAttribute('data-default-image');
179 + imageElem.classList.add('none');
180 + input.value = 'none';
181 + return;
182 + }
183 +
184 + imageElem.src = image.url;
185 + input.value = usingIds ? image.id : image.url;
186 + imageElem.classList.remove('none');
187 + }
188 +
189 + if (action === 'show-image-manager') {
190 + window.ImageManager.showExternal((image) => {
191 + if (!resize) {
192 + setImage(image);
193 + return;
194 + }
195 + let requestString = '/images/thumb/' + image.id + '/' + picker.getAttribute('data-resize-width') + '/' + picker.getAttribute('data-resize-height') + '/' + (resizeCrop ? 'true' : 'false');
196 + $.get(window.baseUrl(requestString), resp => {
197 + image.url = resp.url;
198 + setImage(image);
199 + });
200 + });
201 + } else if (action === 'reset-image') {
202 + setImage({id: 0, url: picker.getAttribute('data-default-image')});
203 + } else if (action === 'remove-image') {
204 + setImage('none');
205 + }
206 +
207 + });
208 +
164 // Detect IE for css 209 // Detect IE for css
165 if(navigator.userAgent.indexOf('MSIE')!==-1 210 if(navigator.userAgent.indexOf('MSIE')!==-1
166 || navigator.appVersion.indexOf('Trident/') > 0 211 || navigator.appVersion.indexOf('Trident/') > 0
......
...@@ -465,4 +465,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { ...@@ -465,4 +465,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
465 border-bottom-width: 3px; 465 border-bottom-width: 3px;
466 } 466 }
467 } 467 }
468 +}
469 +
470 +.image-picker .none {
471 + display: none;
468 } 472 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -31,6 +31,8 @@ return [ ...@@ -31,6 +31,8 @@ return [
31 'delete' => 'Delete', 31 'delete' => 'Delete',
32 'search' => 'Search', 32 'search' => 'Search',
33 'search_clear' => 'Clear Search', 33 'search_clear' => 'Clear Search',
34 + 'reset' => 'Reset',
35 + 'remove' => 'Remove',
34 36
35 37
36 /** 38 /**
......
...@@ -15,5 +15,7 @@ return [ ...@@ -15,5 +15,7 @@ return [
15 'imagem_image_name' => 'Image Name', 15 'imagem_image_name' => 'Image Name',
16 'imagem_delete_confirm' => 'This image is used in the pages below, Click delete again to confirm you want to delete this image.', 16 'imagem_delete_confirm' => 'This image is used in the pages below, Click delete again to confirm you want to delete this image.',
17 'imagem_select_image' => 'Select Image', 17 'imagem_select_image' => 'Select Image',
18 + 'imagem_dropzone' => 'Drop images or click here to upload',
18 'images_deleted' => 'Images Deleted', 19 'images_deleted' => 'Images Deleted',
20 + 'image_preview' => 'Image Preview',
19 ]; 21 ];
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -193,6 +193,7 @@ return [ ...@@ -193,6 +193,7 @@ return [
193 'attachments_link' => 'Attach Link', 193 'attachments_link' => 'Attach Link',
194 'attachments_set_link' => 'Set Link', 194 'attachments_set_link' => 'Set Link',
195 'attachments_delete_confirm' => 'Click delete again to confirm you want to delete this attachment.', 195 'attachments_delete_confirm' => 'Click delete again to confirm you want to delete this attachment.',
196 + 'attachments_dropzone' => 'Drop files or click here to attach a file',
196 'attachments_no_files' => 'No files have been uploaded', 197 'attachments_no_files' => 'No files have been uploaded',
197 'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.', 198 'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.',
198 'attachments_link_name' => 'Link Name', 199 'attachments_link_name' => 'Link Name',
...@@ -204,6 +205,7 @@ return [ ...@@ -204,6 +205,7 @@ return [
204 'attachments_edit_file_name' => 'File Name', 205 'attachments_edit_file_name' => 'File Name',
205 'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite', 206 'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite',
206 'attachments_order_updated' => 'Attachment order updated', 207 'attachments_order_updated' => 'Attachment order updated',
208 + 'attachments_updated_success' => 'Attachment details updated',
207 'attachments_deleted' => 'Attachment deleted', 209 'attachments_deleted' => 'Attachment deleted',
208 210
209 /** 211 /**
......
1 +<div class="image-picker" data-default-image="{{ $defaultImage }}" data-resize-height="{{ $resizeHeight }}" data-resize-width="{{ $resizeWidth }}" data-current-id="{{ $currentId or '' }}" data-resize-crop="{{ $resizeCrop or '' }}">
2 +
3 + <div>
4 + <img @if($currentImage && $currentImage !== 'none') src="{{$currentImage}}" @else src="{{$defaultImage}}" @endif class="{{$imageClass}} @if($currentImage=== 'none') none @endif" alt="{{ trans('components.image_preview') }}">
5 + </div>
6 +
7 + <button class="button" type="button" data-action="show-image-manager">{{ trans('components.imagem_select_image') }}</button>
8 + <br>
9 + <button class="text-button" data-action="reset-image" type="button">{{ trans('common.reset') }}</button>
10 +
11 + @if ($showRemove)
12 + <span class="sep">|</span>
13 + <button class="text-button neg" data-action="remove-image" type="button">{{ trans('common.remove') }}</button>
14 + @endif
15 +
16 + <input type="hidden" name="{{$name}}" id="{{$name}}" value="{{$currentId or $currentImage or ''}}">
17 +</div>
...\ No newline at end of file ...\ No newline at end of file
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
75 </p> 75 </p>
76 </div> 76 </div>
77 <div tab-content="file"> 77 <div tab-content="file">
78 - <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone> 78 + <drop-zone message="{{ trans('entities.attachments_dropzone') }}" upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
79 </div> 79 </div>
80 <div tab-content="link" sub-form="attachLinkSubmit(file)"> 80 <div tab-content="link" sub-form="attachLinkSubmit(file)">
81 <p class="muted small">{{ trans('entities.attachments_explain_link') }}</p> 81 <p class="muted small">{{ trans('entities.attachments_explain_link') }}</p>
...@@ -123,8 +123,8 @@ ...@@ -123,8 +123,8 @@
123 </div> 123 </div>
124 </div> 124 </div>
125 125
126 - <button type="button" class="button" ng-click="cancelEdit()">{{ trans('entities.back') }}</button> 126 + <button type="button" class="button" ng-click="cancelEdit()">{{ trans('common.back') }}</button>
127 - <button type="submit" class="button pos">{{ trans('entities.save') }}</button> 127 + <button type="submit" class="button pos">{{ trans('common.save') }}</button>
128 </div> 128 </div>
129 129
130 </div> 130 </div>
......
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
78 78
79 </div> 79 </div>
80 80
81 - <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone> 81 + <drop-zone message="{{ trans('components.imagem_dropzone') }}" upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
82 82
83 83
84 </div> 84 </div>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
8 8
9 <h1>{{ trans('settings.settings') }}</h1> 9 <h1>{{ trans('settings.settings') }}</h1>
10 10
11 - <form action="{{ baseUrl("/settings") }}" method="POST" ng-cloak> 11 + <form action="{{ baseUrl("/settings") }}" method="POST">
12 {!! csrf_field() !!} 12 {!! csrf_field() !!}
13 13
14 <h3>{{ trans('settings.app_settings') }}</h3> 14 <h3>{{ trans('settings.app_settings') }}</h3>
...@@ -48,7 +48,17 @@ ...@@ -48,7 +48,17 @@
48 <div class="form-group" id="logo-control"> 48 <div class="form-group" id="logo-control">
49 <label for="setting-app-logo">{{ trans('settings.app_logo') }}</label> 49 <label for="setting-app-logo">{{ trans('settings.app_logo') }}</label>
50 <p class="small">{!! trans('settings.app_logo_desc') !!}</p> 50 <p class="small">{!! trans('settings.app_logo_desc') !!}</p>
51 - <image-picker resize-height="43" show-remove="true" resize-width="200" current-image="{{ setting('app-logo', '') }}" default-image="{{ baseUrl('/logo.png') }}" name="setting-app-logo" image-class="logo-image"></image-picker> 51 +
52 + @include('components.image-picker', [
53 + 'resizeHeight' => '43',
54 + 'resizeWidth' => '200',
55 + 'showRemove' => true,
56 + 'defaultImage' => baseUrl('/logo.png'),
57 + 'currentImage' => setting('app-logo'),
58 + 'name' => 'setting-app-logo',
59 + 'imageClass' => 'logo-image'
60 + ])
61 +
52 </div> 62 </div>
53 <div class="form-group" id="color-control"> 63 <div class="form-group" id="color-control">
54 <label for="setting-app-color">{{ trans('settings.app_primary_color') }}</label> 64 <label for="setting-app-color">{{ trans('settings.app_primary_color') }}</label>
......
...@@ -31,7 +31,18 @@ ...@@ -31,7 +31,18 @@
31 <div class="form-group" id="logo-control"> 31 <div class="form-group" id="logo-control">
32 <label for="user-avatar">{{ trans('settings.users_avatar') }}</label> 32 <label for="user-avatar">{{ trans('settings.users_avatar') }}</label>
33 <p class="small">{{ trans('settings.users_avatar_desc') }}</p> 33 <p class="small">{{ trans('settings.users_avatar_desc') }}</p>
34 - <image-picker resize-height="512" resize-width="512" current-image="{{ $user->getAvatar(80) }}" current-id="{{ $user->image_id }}" default-image="{{ baseUrl("/user_avatar.png") }}" name="image_id" show-remove="false" image-class="['avatar' ,'large']"></image-picker> 34 +
35 + @include('components.image-picker', [
36 + 'resizeHeight' => '512',
37 + 'resizeWidth' => '512',
38 + 'showRemove' => false,
39 + 'defaultImage' => baseUrl('/user_avatar.png'),
40 + 'currentImage' => $user->getAvatar(80),
41 + 'currentId' => $user->image_id,
42 + 'name' => 'image_id',
43 + 'imageClass' => 'avatar large'
44 + ])
45 +
35 </div> 46 </div>
36 </div> 47 </div>
37 </div> 48 </div>
......