]> BookStack Code Mirror - bookstack/blob - resources/assets/js/controllers.js
Added uploaded to book/page filters & search in image manager
[bookstack] / resources / assets / js / controllers.js
1 "use strict";
2
3 module.exports = function (ngApp, events) {
4
5     ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService',
6         function ($scope, $attrs, $http, $timeout, imageManagerService) {
7
8             $scope.images = [];
9             $scope.imageType = $attrs.imageType;
10             $scope.selectedImage = false;
11             $scope.dependantPages = false;
12             $scope.showing = false;
13             $scope.hasMore = false;
14             $scope.imageUpdateSuccess = false;
15             $scope.imageDeleteSuccess = false;
16             $scope.uploadedTo = $attrs.uploadedTo;
17             $scope.view = 'all';
18             
19             $scope.searching = false;
20             $scope.searchTerm = '';
21
22             var page = 0;
23             var previousClickTime = 0;
24             var previousClickImage = 0;
25             var dataLoaded = false;
26             var callback = false;
27
28             var preSearchImages = [];
29             var preSearchHasMore = false;
30
31             /**
32              * Used by dropzone to get the endpoint to upload to.
33              * @returns {string}
34              */
35             $scope.getUploadUrl = function () {
36                 return '/images/' + $scope.imageType + '/upload';
37             };
38
39             /**
40              * Cancel the current search operation.
41              */
42             function cancelSearch() {
43                 $scope.searching = false;
44                 $scope.searchTerm = '';
45                 $scope.images = preSearchImages;
46                 $scope.hasMore = preSearchHasMore;
47             }
48             $scope.cancelSearch = cancelSearch;
49             
50
51             /**
52              * Runs on image upload, Adds an image to local list of images
53              * and shows a success message to the user.
54              * @param file
55              * @param data
56              */
57             $scope.uploadSuccess = function (file, data) {
58                 $scope.$apply(() => {
59                     $scope.images.unshift(data);
60                 });
61                 events.emit('success', 'Image uploaded');
62             };
63
64             /**
65              * Runs the callback and hides the image manager.
66              * @param returnData
67              */
68             function callbackAndHide(returnData) {
69                 if (callback) callback(returnData);
70                 $scope.showing = false;
71             }
72
73             /**
74              * Image select action. Checks if a double-click was fired.
75              * @param image
76              */
77             $scope.imageSelect = function (image) {
78                 var dblClickTime = 300;
79                 var currentTime = Date.now();
80                 var timeDiff = currentTime - previousClickTime;
81
82                 if (timeDiff < dblClickTime && image.id === previousClickImage) {
83                     // If double click
84                     callbackAndHide(image);
85                 } else {
86                     // If single
87                     $scope.selectedImage = image;
88                     $scope.dependantPages = false;
89                 }
90                 previousClickTime = currentTime;
91                 previousClickImage = image.id;
92             };
93
94             /**
95              * Action that runs when the 'Select image' button is clicked.
96              * Runs the callback and hides the image manager.
97              */
98             $scope.selectButtonClick = function () {
99                 callbackAndHide($scope.selectedImage);
100             };
101
102             /**
103              * Show the image manager.
104              * Takes a callback to execute later on.
105              * @param doneCallback
106              */
107             function show(doneCallback) {
108                 callback = doneCallback;
109                 $scope.showing = true;
110                 // Get initial images if they have not yet been loaded in.
111                 if (!dataLoaded) {
112                     fetchData();
113                     dataLoaded = true;
114                 }
115             }
116
117             // Connects up the image manger so it can be used externally
118             // such as from TinyMCE.
119             imageManagerService.show = show;
120             imageManagerService.showExternal = function (doneCallback) {
121                 $scope.$apply(() => {
122                     show(doneCallback);
123                 });
124             };
125             window.ImageManager = imageManagerService;
126
127             /**
128              * Hide the image manager
129              */
130             $scope.hide = function () {
131                 $scope.showing = false;
132             };
133
134             var baseUrl = '/images/' + $scope.imageType + '/all/'
135
136             /**
137              * Fetch the list image data from the server.
138              */
139             function fetchData() {
140                 var url = baseUrl + page + '?';
141                 var components = {};
142                 if ($scope.uploadedTo) components['page_id'] = $scope.uploadedTo;
143                 if ($scope.searching) components['term'] = $scope.searchTerm;
144
145
146                 var urlQueryString = Object.keys(components).map((key) => {
147                     return key + '=' + encodeURIComponent(components[key]);
148                 }).join('&');
149                 url += urlQueryString;
150
151                 $http.get(url).then((response) => {
152                     $scope.images = $scope.images.concat(response.data.images);
153                     $scope.hasMore = response.data.hasMore;
154                     page++;
155                 });
156             }
157             $scope.fetchData = fetchData;
158
159             /**
160              * Start a search operation
161              * @param searchTerm
162              */
163             $scope.searchImages = function() {
164
165                 if ($scope.searchTerm === '') {
166                     cancelSearch();
167                     return;
168                 }
169
170                 if (!$scope.searching) {
171                     preSearchImages = $scope.images;
172                     preSearchHasMore = $scope.hasMore;
173                 }
174
175                 $scope.searching = true;
176                 $scope.images = [];
177                 $scope.hasMore = false;
178                 page = 0;
179                 baseUrl = '/images/' + $scope.imageType + '/search/';
180                 fetchData();
181             };
182
183             /**
184              * Set the current image listing view.
185              * @param viewName
186              */
187             $scope.setView = function(viewName) {
188                 cancelSearch();
189                 $scope.images = [];
190                 $scope.hasMore = false;
191                 page = 0;
192                 $scope.view = viewName;
193                 baseUrl = '/images/' + $scope.imageType  + '/' + viewName + '/';
194                 fetchData();
195             }
196
197             /**
198              * Save the details of an image.
199              * @param event
200              */
201             $scope.saveImageDetails = function (event) {
202                 event.preventDefault();
203                 var url = '/images/update/' + $scope.selectedImage.id;
204                 $http.put(url, this.selectedImage).then((response) => {
205                     events.emit('success', 'Image details updated');
206                 }, (response) => {
207                     if (response.status === 422) {
208                         var errors = response.data;
209                         var message = '';
210                         Object.keys(errors).forEach((key) => {
211                             message += errors[key].join('\n');
212                         });
213                         events.emit('error', message);
214                     } else if (response.status === 403) {
215                         events.emit('error', response.data.error);
216                     }
217                 });
218             };
219
220             /**
221              * Delete an image from system and notify of success.
222              * Checks if it should force delete when an image
223              * has dependant pages.
224              * @param event
225              */
226             $scope.deleteImage = function (event) {
227                 event.preventDefault();
228                 var force = $scope.dependantPages !== false;
229                 var url = '/images/' + $scope.selectedImage.id;
230                 if (force) url += '?force=true';
231                 $http.delete(url).then((response) => {
232                     $scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
233                     $scope.selectedImage = false;
234                     events.emit('success', 'Image successfully deleted');
235                 }, (response) => {
236                     // Pages failure
237                     if (response.status === 400) {
238                         $scope.dependantPages = response.data;
239                     } else if (response.status === 403) {
240                         events.emit('error', response.data.error);
241                     }
242                 });
243             };
244
245             /**
246              * Simple date creator used to properly format dates.
247              * @param stringDate
248              * @returns {Date}
249              */
250             $scope.getDate = function (stringDate) {
251                 return new Date(stringDate);
252             };
253
254         }]);
255
256
257     ngApp.controller('BookShowController', ['$scope', '$http', '$attrs', '$sce', function ($scope, $http, $attrs, $sce) {
258         $scope.searching = false;
259         $scope.searchTerm = '';
260         $scope.searchResults = '';
261
262         $scope.searchBook = function (e) {
263             e.preventDefault();
264             var term = $scope.searchTerm;
265             if (term.length == 0) return;
266             $scope.searching = true;
267             $scope.searchResults = '';
268             var searchUrl = '/search/book/' + $attrs.bookId;
269             searchUrl += '?term=' + encodeURIComponent(term);
270             $http.get(searchUrl).then((response) => {
271                 $scope.searchResults = $sce.trustAsHtml(response.data);
272             });
273         };
274
275         $scope.checkSearchForm = function () {
276             if ($scope.searchTerm.length < 1) {
277                 $scope.searching = false;
278             }
279         };
280
281         $scope.clearSearch = function () {
282             $scope.searching = false;
283             $scope.searchTerm = '';
284         };
285
286     }]);
287
288
289     ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce',
290         function ($scope, $http, $attrs, $interval, $timeout, $sce) {
291
292         $scope.editorOptions = require('./pages/page-form');
293         $scope.editContent = '';
294         $scope.draftText = '';
295         var pageId = Number($attrs.pageId);
296         var isEdit = pageId !== 0;
297         var autosaveFrequency = 30; // AutoSave interval in seconds.
298         var isMarkdown = $attrs.editorType === 'markdown';
299         $scope.isUpdateDraft = Number($attrs.pageUpdateDraft) === 1;
300         $scope.isNewPageDraft = Number($attrs.pageNewDraft) === 1;
301
302         // Set inital header draft text
303         if ($scope.isUpdateDraft || $scope.isNewPageDraft) {
304             $scope.draftText = 'Editing Draft'
305         } else {
306             $scope.draftText = 'Editing Page'
307         };
308
309         var autoSave = false;
310
311         var currentContent = {
312             title: false,
313             html: false
314         };
315
316         if (isEdit) {
317             setTimeout(() => {
318                 startAutoSave();
319             }, 1000);
320         }
321
322         // Actions specifically for the markdown editor
323         if (isMarkdown) {
324             $scope.displayContent = '';
325             // Editor change event
326             $scope.editorChange = function (content) {
327                 $scope.displayContent = $sce.trustAsHtml(content);
328             }
329         }
330
331         if (!isMarkdown) {
332             $scope.editorChange = function() {};
333         }
334
335         /**
336          * Start the AutoSave loop, Checks for content change
337          * before performing the costly AJAX request.
338          */
339         function startAutoSave() {
340             currentContent.title = $('#name').val();
341             currentContent.html = $scope.editContent;
342
343             autoSave = $interval(() => {
344                 var newTitle = $('#name').val();
345                 var newHtml = $scope.editContent;
346
347                 if (newTitle !== currentContent.title || newHtml !== currentContent.html) {
348                     currentContent.html = newHtml;
349                     currentContent.title = newTitle;
350                     saveDraft();
351                 }
352
353             }, 1000 * autosaveFrequency);
354         }
355
356         /**
357          * Save a draft update into the system via an AJAX request.
358          * @param title
359          * @param html
360          */
361         function saveDraft() {
362             var data = {
363                 name: $('#name').val(),
364                 html: isMarkdown ? $sce.getTrustedHtml($scope.displayContent) : $scope.editContent
365             };
366
367             if (isMarkdown) data.markdown = $scope.editContent;
368
369             $http.put('/ajax/page/' + pageId + '/save-draft', data).then((responseData) => {
370                 $scope.draftText = responseData.data.message;
371                 if (!$scope.isNewPageDraft) $scope.isUpdateDraft = true;
372             });
373         }
374
375         $scope.forceDraftSave = function() {
376             saveDraft();
377         };
378
379         /**
380          * Discard the current draft and grab the current page
381          * content from the system via an AJAX request.
382          */
383         $scope.discardDraft = function () {
384             $http.get('/ajax/page/' + pageId).then((responseData) => {
385                 if (autoSave) $interval.cancel(autoSave);
386                 $scope.draftText = 'Editing Page';
387                 $scope.isUpdateDraft = false;
388                 $scope.$broadcast('html-update', responseData.data.html);
389                 $scope.$broadcast('markdown-update', responseData.data.markdown || responseData.data.html);
390                 $('#name').val(responseData.data.name);
391                 $timeout(() => {
392                     startAutoSave();
393                 }, 1000);
394                 events.emit('success', 'Draft discarded, The editor has been updated with the current page content');
395             });
396         };
397
398     }]);
399
400 };