]> BookStack Code Mirror - bookstack/commitdiff
Updated image controller styling and added preview option
authorDan Brown <redacted>
Sun, 7 Feb 2016 10:14:11 +0000 (10:14 +0000)
committerDan Brown <redacted>
Sun, 7 Feb 2016 10:17:38 +0000 (10:17 +0000)
The notification system was also updated so it can be used from JavaScript events such as image manager uploads.

Closes #25

resources/assets/js/controllers.js
resources/assets/js/directives.js
resources/assets/js/global.js
resources/assets/js/pages/page-form.js
resources/assets/js/services.js
resources/assets/sass/_image-manager.scss
resources/views/partials/image-manager.blade.php
resources/views/partials/notifications.blade.php

index 5d899075450e87ae12ddc6a180f373b5564e9e39..76def6abd032a006a5743ad8d67178813f5e7d84 100644 (file)
@@ -1,6 +1,6 @@
 "use strict";
 
-module.exports = function (ngApp) {
+module.exports = function (ngApp, events) {
 
     ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService',
         function ($scope, $attrs, $http, $timeout, imageManagerService) {
@@ -17,21 +17,40 @@ module.exports = function (ngApp) {
             var dataLoaded = false;
             var callback = false;
 
+            /**
+             * Simple returns the appropriate upload url depending on the image type set.
+             * @returns {string}
+             */
             $scope.getUploadUrl = function () {
                 return '/images/' + $scope.imageType + '/upload';
             };
 
+            /**
+             * Runs on image upload, Adds an image to local list of images
+             * and shows a success message to the user.
+             * @param file
+             * @param data
+             */
             $scope.uploadSuccess = function (file, data) {
                 $scope.$apply(() => {
                     $scope.images.unshift(data);
                 });
+                events.emit('success', 'Image uploaded');
             };
 
+            /**
+             * Runs the callback and hides the image manager.
+             * @param returnData
+             */
             function callbackAndHide(returnData) {
                 if (callback) callback(returnData);
                 $scope.showing = false;
             }
 
+            /**
+             * Image select action. Checks if a double-click was fired.
+             * @param image
+             */
             $scope.imageSelect = function (image) {
                 var dblClickTime = 300;
                 var currentTime = Date.now();
@@ -48,10 +67,19 @@ module.exports = function (ngApp) {
                 previousClickTime = currentTime;
             };
 
+            /**
+             * Action that runs when the 'Select image' button is clicked.
+             * Runs the callback and hides the image manager.
+             */
             $scope.selectButtonClick = function () {
                 callbackAndHide($scope.selectedImage);
             };
 
+            /**
+             * Show the image manager.
+             * Takes a callback to execute later on.
+             * @param doneCallback
+             */
             function show(doneCallback) {
                 callback = doneCallback;
                 $scope.showing = true;
@@ -62,6 +90,8 @@ module.exports = function (ngApp) {
                 }
             }
 
+            // Connects up the image manger so it can be used externally
+            // such as from TinyMCE.
             imageManagerService.show = show;
             imageManagerService.showExternal = function (doneCallback) {
                 $scope.$apply(() => {
@@ -70,10 +100,16 @@ module.exports = function (ngApp) {
             };
             window.ImageManager = imageManagerService;
 
+            /**
+             * Hide the image manager
+             */
             $scope.hide = function () {
                 $scope.showing = false;
             };
 
+            /**
+             * Fetch the list image data from the server.
+             */
             function fetchData() {
                 var url = '/images/' + $scope.imageType + '/all/' + page;
                 $http.get(url).then((response) => {
@@ -82,28 +118,33 @@ module.exports = function (ngApp) {
                     page++;
                 });
             }
+            $scope.fetchData = fetchData;
 
+            /**
+             * Save the details of an image.
+             * @param event
+             */
             $scope.saveImageDetails = function (event) {
                 event.preventDefault();
                 var url = '/images/update/' + $scope.selectedImage.id;
                 $http.put(url, this.selectedImage).then((response) => {
-                    $scope.imageUpdateSuccess = true;
-                    $timeout(() => {
-                        $scope.imageUpdateSuccess = false;
-                    }, 3000);
+                    events.emit('success', 'Image details updated');
                 }, (response) => {
                     var errors = response.data;
                     var message = '';
                     Object.keys(errors).forEach((key) => {
                         message += errors[key].join('\n');
                     });
-                    $scope.imageUpdateFailure = message;
-                    $timeout(() => {
-                        $scope.imageUpdateFailure = false;
-                    }, 5000);
+                    events.emit('error', message);
                 });
             };
 
+            /**
+             * Delete an image from system and notify of success.
+             * Checks if it should force delete when an image
+             * has dependant pages.
+             * @param event
+             */
             $scope.deleteImage = function (event) {
                 event.preventDefault();
                 var force = $scope.dependantPages !== false;
@@ -112,10 +153,7 @@ module.exports = function (ngApp) {
                 $http.delete(url).then((response) => {
                     $scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
                     $scope.selectedImage = false;
-                    $scope.imageDeleteSuccess = true;
-                    $timeout(() => {
-                        $scope.imageDeleteSuccess = false;
-                    }, 3000);
+                    events.emit('success', 'Image successfully deleted');
                 }, (response) => {
                     // Pages failure
                     if (response.status === 400) {
@@ -124,6 +162,15 @@ module.exports = function (ngApp) {
                 });
             };
 
+            /**
+             * Simple date creator used to properly format dates.
+             * @param stringDate
+             * @returns {Date}
+             */
+            $scope.getDate = function(stringDate) {
+                return new Date(stringDate);
+            };
+
         }]);
 
 
index 6ccfcf8558cf4c74c9b269c44c33d4ab26b0679e..60abde6e928be202ffd808f62d785b97e2cc4c88 100644 (file)
@@ -5,7 +5,7 @@ var toggleSwitchTemplate = require('./components/toggle-switch.html');
 var imagePickerTemplate = require('./components/image-picker.html');
 var dropZoneTemplate = require('./components/drop-zone.html');
 
-module.exports = function (ngApp) {
+module.exports = function (ngApp, events) {
 
     /**
      * Toggle Switches
index 5b0b6f9f0600b0fc289998400c26c0336589b517..00c5f13c2103a67f51923a654c12be5cdff008f5 100644 (file)
@@ -1,4 +1,4 @@
-
+"use strict";
 
 // AngularJS - Create application and load components
 var angular = require('angular');
@@ -7,9 +7,31 @@ var ngAnimate = require('angular-animate');
 var ngSanitize = require('angular-sanitize');
 
 var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize']);
-var services = require('./services')(ngApp);
-var directives = require('./directives')(ngApp);
-var controllers = require('./controllers')(ngApp);
+
+
+// Global Event System
+var Events = {
+    listeners: {},
+    emit: function (eventName, eventData) {
+        if (typeof this.listeners[eventName] === 'undefined') return this;
+        var eventsToStart = this.listeners[eventName];
+        for (let i = 0; i < eventsToStart.length; i++) {
+            var event = eventsToStart[i];
+            event(eventData);
+        }
+        return this;
+    },
+    listen: function (eventName, callback) {
+        if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
+        this.listeners[eventName].push(callback);
+        return this;
+    }
+};
+window.Events = Events;
+
+var services = require('./services')(ngApp, Events);
+var directives = require('./directives')(ngApp, Events);
+var controllers = require('./controllers')(ngApp, Events);
 
 //Global jQuery Config & Extensions
 
@@ -32,8 +54,25 @@ $.expr[":"].contains = $.expr.createPseudo(function (arg) {
 // Global jQuery Elements
 $(function () {
 
+
+    var notifications = $('.notification');
+    var successNotification = notifications.filter('.pos');
+    var errorNotification = notifications.filter('.neg');
+    // Notification Events
+    window.Events.listen('success', function (text) {
+        successNotification.hide();
+        successNotification.find('span').text(text);
+        setTimeout(() => {
+            successNotification.show();
+        }, 1);
+    });
+    window.Events.listen('error', function (text) {
+        errorNotification.find('span').text(text);
+        errorNotification.show();
+    });
+
     // Notification hiding
-    $('.notification').click(function () {
+    notifications.click(function () {
         $(this).fadeOut(100);
     });
 
index 756a9211baefe1354c893d1ae6e7584d49c7749f..290b7c6534da0f853c9b5dda424c02a43ae7f427 100644 (file)
@@ -8,7 +8,6 @@ module.exports = {
     statusbar: false,
     menubar: false,
     paste_data_images: false,
-    //height: 700,
     extended_valid_elements: 'pre[*]',
     automatic_uploads: false,
     valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
@@ -31,7 +30,7 @@ module.exports = {
         alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
     },
     file_browser_callback: function (field_name, url, type, win) {
-        ImageManager.show(function (image) {
+        window.ImageManager.showExternal(function (image) {
             win.document.getElementById(field_name).value = image.url;
             if ("createEvent" in document) {
                 var evt = document.createEvent("HTMLEvents");
@@ -40,6 +39,10 @@ module.exports = {
             } else {
                 win.document.getElementById(field_name).fireEvent("onchange");
             }
+            var html = '<a href="' + image.url + '" target="_blank">';
+            html += '<img src="' + image.thumbs.display + '" alt="' + image.name + '">';
+            html += '</a>';
+            win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
         });
     },
     paste_preprocess: function (plugin, args) {
index 684a6845008814000cf730e50a29486ad5a91ea7..cd2759c546dad7bbd2bae6d879b8e3fc4f574e0d 100644 (file)
@@ -1,6 +1,6 @@
 "use strict";
 
-module.exports = function(ngApp) {
+module.exports = function(ngApp, events) {
 
     ngApp.factory('imageManagerService', function() {
         return {
index babbad0c10395aa471816226fa647ed67871e817..8b18d24f3966abce4da2182dd597e3da4944a8c3 100644 (file)
@@ -21,7 +21,6 @@
   border-radius: 4px;
   box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
   overflow: hidden;
-  max-width: 1340px;
   position: fixed;
   top: 0;
   bottom: 0;
   right: 0;
 }
 
-.image-manager-list img {
+.image-manager-list .image {
   display: block;
+  position: relative;
   border-radius: 0;
   float: left;
   margin: 0;
   cursor: pointer;
   width: (100%/6);
   height: auto;
-  border: 1px solid #FFF;
+  border: 1px solid #DDD;
+  box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
   transition: all cubic-bezier(.4, 0, 1, 1) 160ms;
+  overflow: hidden;
   &.selected {
     transform: scale3d(0.92, 0.92, 0.92);
+    border: 1px solid #444;
+    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
+  }
+  img {
+    width: 100%;
+    max-width: 100%;
+    display: block;
+  }
+  .image-meta {
+    position: absolute;
+    width: 100%;
+    bottom: 0;
+    left: 0;
+    color: #EEE;
+    background-color: rgba(0, 0, 0, 0.4);
+    font-size: 10px;
+    padding: 3px 4px;
+    span {
+      display: block;
+    }
+  }
+  @include smaller-than($xl) {
+    width: (100%/4);
+  }
+  @include smaller-than($m) {
+    .image-meta {
+      display: none;
+    }
   }
 }
 
index 6dc528d23df775854a560617c140133fa1dc59b7..bf7bf445cf3f5de27bb6a27005e77359e075da8f 100644 (file)
@@ -5,11 +5,14 @@
             <div class="image-manager-content">
                 <div class="image-manager-list">
                     <div ng-repeat="image in images">
-                        <img class="anim fadeIn"
-                             ng-class="{selected: (image==selectedImage)}"
-                             ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}"
-                            ng-click="imageSelect(image)"
-                            ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}">
+                        <div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"
+                             ng-class="{selected: (image==selectedImage)}" ng-click="imageSelect(image)">
+                            <img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
+                            <div class="image-meta">
+                                <span class="name" ng-bind="image.name"></span>
+                                <span class="date">Uploaded @{{ getDate(image.created_at) | date:'mediumDate' }}</span>
+                            </div>
+                        </div>
                     </div>
                     <div class="load-more" ng-show="hasMore" ng-click="fetchData()">Load More</div>
                 </div>
 
             <div class="image-manager-sidebar">
                 <h2>Images</h2>
-                <hr class="even">
                 <drop-zone upload-url="@{{getUploadUrl()}}" event-success="uploadSuccess"></drop-zone>
                 <div class="image-manager-details anim fadeIn" ng-show="selectedImage">
 
                     <hr class="even">
 
                     <form ng-submit="saveImageDetails($event)">
+                        <div>
+                            <a ng-href="@{{selectedImage.url}}" target="_blank" style="display: block;">
+                                <img ng-src="@{{selectedImage.thumbs.gallery}}" ng-attr-alt="@{{selectedImage.title}}" ng-attr-title="@{{selectedImage.name}}">
+                            </a>
+                        </div>
                         <div class="form-group">
                             <label for="name">Image Name</label>
                             <input type="text" id="name" name="name" ng-model="selectedImage.name">
-                            <p class="text-pos text-small" ng-show="imageUpdateSuccess"><i class="fa fa-check"></i> Image name updated</p>
-                            <p class="text-neg text-small" ng-show="imageUpdateFailure"><i class="fa fa-times"></i> <span ng-bind="imageUpdateFailure"></span></p>
                         </div>
                     </form>
 
@@ -53,8 +58,6 @@
                     </form>
                 </div>
 
-                <p class="text-pos" ng-show="imageDeleteSuccess"><i class="fa fa-check"></i> Image deleted</p>
-
                 <div class="image-manager-bottom">
                     <button class="button pos anim fadeIn" ng-show="selectedImage" ng-click="selectButtonClick()">
                         <i class="zmdi zmdi-square-right"></i>Select Image
index 6bb00e237978c049a2bef5e3fa80d938053427ef..8cc0774c9971a0f9fd20c8b43a1445ea9950effd 100644 (file)
@@ -1,11 +1,8 @@
-@if(Session::has('success'))
-    <div class="notification anim pos">
-        <i class="zmdi zmdi-mood"></i> <span>{{ Session::get('success') }}</span>
-    </div>
-@endif
 
-@if(Session::has('error'))
-    <div class="notification anim neg stopped">
-        <i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
-    </div>
-@endif
\ No newline at end of file
+<div class="notification anim pos" @if(!Session::has('success')) style="display:none;" @endif>
+    <i class="zmdi zmdi-check-circle"></i> <span>{{ Session::get('success') }}</span>
+</div>
+
+<div class="notification anim neg stopped" @if(!Session::has('error')) style="display:none;" @endif>
+    <i class="zmdi zmdi-alert-circle"></i> <span>{{ Session::get('error') }}</span>
+</div>