]> BookStack Code Mirror - bookstack/commitdiff
Added link selector interface to WYSIWYG editor
authorDan Brown <redacted>
Thu, 1 Sep 2016 19:36:22 +0000 (20:36 +0100)
committerDan Brown <redacted>
Thu, 1 Sep 2016 19:36:22 +0000 (20:36 +0100)
resources/assets/js/directives.js
resources/assets/js/global.js
resources/assets/js/pages/page-form.js
resources/assets/sass/_buttons.scss
resources/assets/sass/_components.scss
resources/views/books/list-item.blade.php
resources/views/chapters/list-item.blade.php
resources/views/pages/edit.blade.php
resources/views/pages/list-item.blade.php

index fc7b88259eccb92b17e4a2305abe75d96bd66027..94e28151215200e7597d4c36bf8d376e0f255cdb 100644 (file)
@@ -600,6 +600,58 @@ module.exports = function (ngApp, events) {
         }
     }]);
 
         }
     }]);
 
+    ngApp.directive('entityLinkSelector', [function($http) {
+        return {
+            restict: 'A',
+            link: function(scope, element, attrs) {
+
+                const selectButton = element.find('.entity-link-selector-confirm');
+                let callback = false;
+                let entitySelection = null;
+
+                // Handle entity selection change, Stores the selected entity locally
+                function entitySelectionChange(entity) {
+                    entitySelection = entity;
+                    if (entity === null) {
+                        selectButton.attr('disabled', 'true');
+                    } else {
+                        selectButton.removeAttr('disabled');
+                    }
+                }
+                events.listen('entity-select-change', entitySelectionChange);
+
+                // Handle selection confirm button click
+                selectButton.click(event => {
+                    hide();
+                    if (entitySelection !== null) callback(entitySelection);
+                });
+
+                // Show selector interface
+                function show() {
+                    element.fadeIn(240);
+                }
+
+                // Hide selector interface
+                function hide() {
+                    element.fadeOut(240);
+                }
+
+                // Listen to confirmation of entity selections (doubleclick)
+                events.listen('entity-select-confirm', entity => {
+                    hide();
+                    callback(entity);
+                });
+
+                // Show entity selector, Accessible globally, and store the callback
+                window.showEntityLinkSelector = function(passedCallback) {
+                    show();
+                    callback = passedCallback;
+                };
+
+            }
+        };
+    }]);
+
 
     ngApp.directive('entitySelector', ['$http', '$sce', function ($http, $sce) {
         return {
 
     ngApp.directive('entitySelector', ['$http', '$sce', function ($http, $sce) {
         return {
@@ -613,26 +665,60 @@ module.exports = function (ngApp, events) {
                 // Add input for forms
                 const input = element.find('[entity-selector-input]').first();
 
                 // Add input for forms
                 const input = element.find('[entity-selector-input]').first();
 
+                // Detect double click events
+                var lastClick = 0;
+                function isDoubleClick() {
+                    let now = Date.now();
+                    let answer = now - lastClick < 300;
+                    lastClick = now;
+                    return answer;
+                }
+
                 // Listen to entity item clicks
                 element.on('click', '.entity-list a', function(event) {
                     event.preventDefault();
                     event.stopPropagation();
                     let item = $(this).closest('[data-entity-type]');
                 // Listen to entity item clicks
                 element.on('click', '.entity-list a', function(event) {
                     event.preventDefault();
                     event.stopPropagation();
                     let item = $(this).closest('[data-entity-type]');
-                    itemSelect(item);
+                    itemSelect(item, isDoubleClick());
                 });
                 element.on('click', '[data-entity-type]', function(event) {
                 });
                 element.on('click', '[data-entity-type]', function(event) {
-                    itemSelect($(this));
+                    itemSelect($(this), isDoubleClick());
                 });
 
                 // Select entity action
                 });
 
                 // Select entity action
-                function itemSelect(item) {
+                function itemSelect(item, doubleClick) {
                     let entityType = item.attr('data-entity-type');
                     let entityId = item.attr('data-entity-id');
                     let entityType = item.attr('data-entity-type');
                     let entityId = item.attr('data-entity-id');
-                    let isSelected = !item.hasClass('selected');
+                    let isSelected = !item.hasClass('selected') || doubleClick;
                     element.find('.selected').removeClass('selected').removeClass('primary-background');
                     if (isSelected) item.addClass('selected').addClass('primary-background');
                     let newVal = isSelected ? `${entityType}:${entityId}` : '';
                     input.val(newVal);
                     element.find('.selected').removeClass('selected').removeClass('primary-background');
                     if (isSelected) item.addClass('selected').addClass('primary-background');
                     let newVal = isSelected ? `${entityType}:${entityId}` : '';
                     input.val(newVal);
+
+                    if (!isSelected) {
+                        events.emit('entity-select-change', null);
+                    }
+
+                    if (!doubleClick && !isSelected) return;
+
+                    let link = item.find('.entity-list-item-link').attr('href');
+                    let name = item.find('.entity-list-item-name').text();
+
+                    if (doubleClick) {
+                        events.emit('entity-select-confirm', {
+                            id: Number(entityId),
+                            name: name,
+                            link: link
+                        });
+                    }
+
+                    if (isSelected) {
+                        events.emit('entity-select-change', {
+                            id: Number(entityId),
+                            name: name,
+                            link: link
+                        });
+                    }
                 }
 
                 // Get search url with correct types
                 }
 
                 // Get search url with correct types
index cc868a0eae0bc4bd93a68de286ce655b382751e2..eb275096595f34d33ed9b77f68848821d4dac876 100644 (file)
@@ -135,6 +135,11 @@ $(function () {
         $(this).closest('.overlay').fadeOut(240);
     });
 
         $(this).closest('.overlay').fadeOut(240);
     });
 
+    $('.overlay').click(function(event) {
+        if (!$(event.target).hasClass('overlay')) return;
+        $(this).fadeOut(240);
+    });
+
 });
 
 // Page specific items
 });
 
 // Page specific items
index b733494fa59d26a8a6ec3d3ea636e16e4cd8c6b7..d8f298959e130e862075d78339c2c82cbfbaca8c 100644 (file)
@@ -96,26 +96,37 @@ var mceOptions = module.exports = {
     },
     file_browser_callback: function (field_name, url, type, win) {
 
     },
     file_browser_callback: function (field_name, url, type, win) {
 
-        // Show image manager
-        window.ImageManager.showExternal(function (image) {
-
-            // Set popover link input to image url then fire change event
-            // to ensure the new value sticks
-            win.document.getElementById(field_name).value = image.url;
-            if ("createEvent" in document) {
-                var evt = document.createEvent("HTMLEvents");
-                evt.initEvent("change", false, true);
-                win.document.getElementById(field_name).dispatchEvent(evt);
-            } else {
-                win.document.getElementById(field_name).fireEvent("onchange");
-            }
+        if (type === 'file') {
+            window.showEntityLinkSelector(function(entity) {
+                var originalField = win.document.getElementById(field_name);
+                originalField.value = entity.link;
+                $(originalField).closest('.mce-form').find('input').eq(2).val(entity.name);
+            });
+        }
+
+        if (type === 'image') {
+            // Show image manager
+            window.ImageManager.showExternal(function (image) {
+
+                // Set popover link input to image url then fire change event
+                // to ensure the new value sticks
+                win.document.getElementById(field_name).value = image.url;
+                if ("createEvent" in document) {
+                    var evt = document.createEvent("HTMLEvents");
+                    evt.initEvent("change", false, true);
+                    win.document.getElementById(field_name).dispatchEvent(evt);
+                } else {
+                    win.document.getElementById(field_name).fireEvent("onchange");
+                }
+
+                // Replace the actively selected content with the linked image
+                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);
+            });
+        }
 
 
-            // Replace the actively selected content with the linked image
-            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) {
         var content = args.content;
     },
     paste_preprocess: function (plugin, args) {
         var content = args.content;
index 5bdb0cf2857fd7ef1b9534558ee5202080461662..5de8896735aa9799ef5a4b12cf04b098c0366b42 100644 (file)
@@ -100,3 +100,13 @@ $button-border-radius: 2px;
   }
 }
 
   }
 }
 
+.button[disabled] {
+  background-color: #BBB;
+  cursor: default;
+  &:hover {
+    background-color: #BBB;
+    cursor: default;
+    box-shadow: none;
+  }
+}
+
index a41277e6f3347ca06a023de3b5369c814e50db9e..544001261eea480d2a3a3a971ed54b21780d3b73 100644 (file)
   }
 }
 
   }
 }
 
-.popup-header {
+.corner-button {
+  position: absolute;
+  top: 0;
+  right: 0;
+  margin: 0;
+  height: 40px;
+  border-radius: 0;
+  box-shadow: none;
+}
+
+.popup-header, .popup-footer {
   display: block;
   position: relative;
   height: 40px;
   display: block;
   position: relative;
   height: 40px;
-  .popup-close {
-    position: absolute;
-    top: 0;
-    right: 0;
-    margin: 0;
-    height: 40px;
-    border-radius: 0;
-    box-shadow: none;
-  }
   .popup-title {
     color: #FFF;
     padding: 8px $-m;
   }
 }
   .popup-title {
     color: #FFF;
     padding: 8px $-m;
   }
 }
-
+#entity-selector-wrap .popup-body .form-group {
+  margin: 0;
+}
 .image-manager-body {
   min-height: 60vh;
 }
 .image-manager-body {
   min-height: 60vh;
 }
index 945eb901563c1a9b64a8e02afe79ba4cf5278a30..2eefdfbf531c0a9917cc2606a344e32402863ffd 100644 (file)
@@ -1,5 +1,5 @@
 <div class="book entity-list-item"  data-entity-type="book" data-entity-id="{{$book->id}}">
 <div class="book entity-list-item"  data-entity-type="book" data-entity-id="{{$book->id}}">
-    <h3 class="text-book"><a class="text-book" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></h3>
+    <h3 class="text-book"><a class="text-book entity-list-item-link" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i><span class="entity-list-item-name">{{$book->name}}</span></a></h3>
     @if(isset($book->searchSnippet))
         <p class="text-muted">{!! $book->searchSnippet !!}</p>
     @else
     @if(isset($book->searchSnippet))
         <p class="text-muted">{!! $book->searchSnippet !!}</p>
     @else
index 3677851df77dc712b0faa200de480e0b757c0cf2..35d3a7589d927b313e8e8488c8ff2490cc82a289 100644 (file)
@@ -6,8 +6,8 @@
             </a>
             <span class="text-muted">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</span>
         @endif
             </a>
             <span class="text-muted">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</span>
         @endif
-        <a href="{{ $chapter->getUrl() }}" class="text-chapter">
-            <i class="zmdi zmdi-collection-bookmark"></i>{{ $chapter->name }}
+        <a href="{{ $chapter->getUrl() }}" class="text-chapter entity-list-item-link">
+            <i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
         </a>
     </h3>
     @if(isset($chapter->searchSnippet))
         </a>
     </h3>
     @if(isset($chapter->searchSnippet))
index f470d8d8005ddb5fa82a3ef9f64b2a993fc278da..ad569a3274629c260e890b8e03c6fe537216d0e1 100644 (file)
     @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
 
     <div id="entity-selector-wrap">
     @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
 
     <div id="entity-selector-wrap">
-        <div class="overlay">
+        <div class="overlay" entity-link-selector>
             <div class="popup-body small flex-child">
                 <div class="popup-header primary-background">
                     <div class="popup-title">Entity Select</div>
             <div class="popup-body small flex-child">
                 <div class="popup-header primary-background">
                     <div class="popup-title">Entity Select</div>
-                    <button class="popup-close neg button">x</button>
+                    <button type="button" class="corner-button neg button">x</button>
                 </div>
                 @include('partials/entity-selector', ['name' => 'entity-selector'])
                 </div>
                 @include('partials/entity-selector', ['name' => 'entity-selector'])
+                <div class="popup-footer">
+                    <button type="button" disabled="true" class="button entity-link-selector-confirm pos corner-button">Select</button>
+                </div>
             </div>
         </div>
     </div>
 
             </div>
         </div>
     </div>
 
+    <script>
+        (function() {
+
+        })();
+    </script>
+
 @stop
\ No newline at end of file
 @stop
\ No newline at end of file
index a95870db044725453b3404daf20d94e6796b08ea..98243f6fa88ba63a2d09c20af659917025348186 100644 (file)
@@ -1,6 +1,6 @@
 <div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
     <h3>
 <div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
     <h3>
-        <a href="{{ $page->getUrl() }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ $page->name }}</a>
+        <a href="{{ $page->getUrl() }}" class="text-page entity-list-item-link"><i class="zmdi zmdi-file-text"></i><span class="entity-list-item-name">{{ $page->name }}</span></a>
     </h3>
 
     @if(isset($page->searchSnippet))
     </h3>
 
     @if(isset($page->searchSnippet))