]> BookStack Code Mirror - bookstack/commitdiff
Added tag autosuggestion when no input provided
authorDan Brown <redacted>
Sat, 4 Jun 2016 14:37:28 +0000 (15:37 +0100)
committerDan Brown <redacted>
Sat, 4 Jun 2016 14:37:28 +0000 (15:37 +0100)
Shows the most popular tag names/values.
As requested on #121

app/Http/Controllers/TagController.php
app/Repos/TagRepo.php
resources/assets/js/directives.js

index b6749aec10cfc2ab40243b49f425127446571ad9..c8a35654108ca6bde5d3429515711d702342b8e2 100644 (file)
@@ -55,7 +55,7 @@ class TagController extends Controller
      */
     public function getNameSuggestions(Request $request)
     {
-        $searchTerm = $request->get('search');
+        $searchTerm = $request->has('search') ? $request->get('search') : false;
         $suggestions = $this->tagRepo->getNameSuggestions($searchTerm);
         return response()->json($suggestions);
     }
@@ -66,7 +66,7 @@ class TagController extends Controller
      */
     public function getValueSuggestions(Request $request)
     {
-        $searchTerm = $request->get('search');
+        $searchTerm = $request->has('search') ? $request->get('search') : false;
         $tagName = $request->has('name') ? $request->get('name') : false;
         $suggestions = $this->tagRepo->getValueSuggestions($searchTerm, $tagName);
         return response()->json($suggestions);
index e87732cf591fd5d9c6fb55767bd2aaa1da7fd488..6d0857f8b234ead8a7b3cb0fbf4f34871f5ca4b6 100644 (file)
@@ -58,34 +58,48 @@ class TagRepo
 
     /**
      * Get tag name suggestions from scanning existing tag names.
+     * If no search term is given the 50 most popular tag names are provided.
      * @param $searchTerm
      * @return array
      */
-    public function getNameSuggestions($searchTerm)
+    public function getNameSuggestions($searchTerm = false)
     {
-        if ($searchTerm === '') return [];
-        $query = $this->tag->where('name', 'LIKE', $searchTerm . '%')->groupBy('name')->orderBy('name', 'desc');
+        $query = $this->tag->select('*', \DB::raw('count(*) as count'))->groupBy('name');
+
+        if ($searchTerm) {
+            $query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'desc');
+        } else {
+            $query = $query->orderBy('count', 'desc')->take(50);
+        }
+
         $query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
         return $query->get(['name'])->pluck('name');
     }
 
     /**
      * Get tag value suggestions from scanning existing tag values.
+     * If no search is given the 50 most popular values are provided.
+     * Passing a tagName will only find values for a tags with a particular name.
      * @param $searchTerm
      * @param $tagName
      * @return array
      */
-    public function getValueSuggestions($searchTerm, $tagName = false)
+    public function getValueSuggestions($searchTerm = false, $tagName = false)
     {
-        if ($searchTerm === '') return [];
-        $query = $this->tag->where('value', 'LIKE', $searchTerm . '%')->groupBy('value')->orderBy('value', 'desc');
-        if ($tagName !== false) {
-            $query = $query->where('name', '=', $tagName);
+        $query = $this->tag->select('*', \DB::raw('count(*) as count'))->groupBy('value');
+
+        if ($searchTerm) {
+            $query = $query->where('value', 'LIKE', $searchTerm . '%')->orderBy('value', 'desc');
+        } else {
+            $query = $query->orderBy('count', 'desc')->take(50);
         }
+
+        if ($tagName !== false) $query = $query->where('name', '=', $tagName);
+
         $query = $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
         return $query->get(['value'])->pluck('value');
     }
-    
+
     /**
      * Save an array of tags to an entity
      * @param Entity $entity
index df5284a9772cef9cb59697519f89906f3d519ef8..43d55f092eab39e6e370c26e6039c7dbb53ae261 100644 (file)
@@ -166,7 +166,7 @@ module.exports = function (ngApp, events) {
         };
     }]);
 
-    ngApp.directive('tinymce', ['$timeout', function($timeout) {
+    ngApp.directive('tinymce', ['$timeout', function ($timeout) {
         return {
             restrict: 'A',
             scope: {
@@ -204,8 +204,8 @@ module.exports = function (ngApp, events) {
                 scope.tinymce.extraSetups.push(tinyMceSetup);
 
                 // Custom tinyMCE plugins
-                tinymce.PluginManager.add('customhr', function(editor) {
-                    editor.addCommand('InsertHorizontalRule', function() {
+                tinymce.PluginManager.add('customhr', function (editor) {
+                    editor.addCommand('InsertHorizontalRule', function () {
                         var hrElem = document.createElement('hr');
                         var cNode = editor.selection.getNode();
                         var parentNode = cNode.parentNode;
@@ -231,7 +231,7 @@ module.exports = function (ngApp, events) {
         }
     }]);
 
-    ngApp.directive('markdownInput', ['$timeout', function($timeout) {
+    ngApp.directive('markdownInput', ['$timeout', function ($timeout) {
         return {
             restrict: 'A',
             scope: {
@@ -255,7 +255,7 @@ module.exports = function (ngApp, events) {
 
                 scope.$on('markdown-update', (event, value) => {
                     element.val(value);
-                    scope.mdModel= value;
+                    scope.mdModel = value;
                     scope.mdChange(markdown(value));
                 });
 
@@ -263,7 +263,7 @@ module.exports = function (ngApp, events) {
         }
     }]);
 
-    ngApp.directive('markdownEditor', ['$timeout', function($timeout) {
+    ngApp.directive('markdownEditor', ['$timeout', function ($timeout) {
         return {
             restrict: 'A',
             link: function (scope, element, attrs) {
@@ -303,7 +303,7 @@ module.exports = function (ngApp, events) {
                     if (now - lastScroll > scrollDebounceTime) {
                         setScrollHeights()
                     }
-                    let scrollPercent = (input.scrollTop() / (inputScrollHeight-inputHeight));
+                    let scrollPercent = (input.scrollTop() / (inputScrollHeight - inputHeight));
                     let displayScrollY = (displayScrollHeight - displayHeight) * scrollPercent;
                     display.scrollTop(displayScrollY);
                     lastScroll = now;
@@ -341,11 +341,11 @@ module.exports = function (ngApp, events) {
             }
         }
     }]);
-    
-    ngApp.directive('toolbox', [function() {
+
+    ngApp.directive('toolbox', [function () {
         return {
             restrict: 'A',
-            link: function(scope, elem, attrs) {
+            link: function (scope, elem, attrs) {
 
                 // Get common elements
                 const $buttons = elem.find('[tab-button]');
@@ -356,7 +356,7 @@ module.exports = function (ngApp, events) {
                 $toggle.click((e) => {
                     elem.toggleClass('open');
                 });
-                
+
                 // Set an active tab/content by name
                 function setActive(tabName, openToolbox) {
                     $buttons.removeClass('active');
@@ -370,7 +370,7 @@ module.exports = function (ngApp, events) {
                 setActive($content.first().attr('tab-content'), false);
 
                 // Handle tab button click
-                $buttons.click(function(e) {
+                $buttons.click(function (e) {
                     let name = $(this).attr('tab-button');
                     setActive(name, true);
                 });
@@ -378,11 +378,11 @@ module.exports = function (ngApp, events) {
         }
     }]);
 
-    ngApp.directive('tagAutosuggestions', ['$http', function($http) {
+    ngApp.directive('tagAutosuggestions', ['$http', function ($http) {
         return {
             restrict: 'A',
-            link: function(scope, elem, attrs) {
-                
+            link: function (scope, elem, attrs) {
+
                 // Local storage for quick caching.
                 const localCache = {};
 
@@ -399,49 +399,49 @@ module.exports = function (ngApp, events) {
                 let active = 0;
 
                 // Listen to input events on autosuggest fields
-                elem.on('input', '[autosuggest]', function(event) {
+                elem.on('input focus', '[autosuggest]', function (event) {
                     let $input = $(this);
                     let val = $input.val();
                     let url = $input.attr('autosuggest');
                     let type = $input.attr('autosuggest-type');
                     
-                    // No suggestions until at least 3 chars
-                    if (val.length < 3) {
-                        if (isShowing) {
-                            $suggestionBox.hide();
-                            isShowing = false;
-                        }
-                        return;
-                    }
-
                     // Add name param to request if for a value
                     if (type.toLowerCase() === 'value') {
                         let $nameInput = $input.closest('tr').find('[autosuggest-type="name"]').first();
                         let nameVal = $nameInput.val();
-                        if (nameVal === '') return;
-                        url += '?name=' + encodeURIComponent(nameVal);
-                        console.log(url);
+                        if (nameVal !== '') {
+                            url += '?name=' + encodeURIComponent(nameVal);
+                        }
                     }
 
                     let suggestionPromise = getSuggestions(val.slice(0, 3), url);
                     suggestionPromise.then(suggestions => {
-                       if (val.length > 2) {
-                           suggestions = suggestions.filter(item => {
-                               return item.toLowerCase().indexOf(val.toLowerCase()) !== -1;
-                           }).slice(0, 4);
-                           displaySuggestions($input, suggestions);
-                       }
+                        if (val.length === 0) {
+                            displaySuggestions($input, suggestions.slice(0, 6));
+                        } else  {
+                            suggestions = suggestions.filter(item => {
+                                return item.toLowerCase().indexOf(val.toLowerCase()) !== -1;
+                            }).slice(0, 4);
+                            displaySuggestions($input, suggestions);
+                        }
                     });
                 });
 
                 // Hide autosuggestions when input loses focus.
                 // Slight delay to allow clicks.
-                elem.on('blur', '[autosuggest]', function(event) {
+                let lastFocusTime = 0;
+                elem.on('blur', '[autosuggest]', function (event) {
+                    let startTime = Date.now();
                     setTimeout(() => {
-                        $suggestionBox.hide();
-                        isShowing = false;
+                        if (lastFocusTime < startTime) {
+                            $suggestionBox.hide();
+                            isShowing = false;
+                        }
                     }, 200)
                 });
+                elem.on('focus', '[autosuggest]', function (event) {
+                    lastFocusTime = Date.now();
+                });
 
                 elem.on('keydown', '[autosuggest]', function (event) {
                     if (!isShowing) return;
@@ -451,12 +451,12 @@ module.exports = function (ngApp, events) {
 
                     // Down arrow
                     if (event.keyCode === 40) {
-                        let newActive = (active === suggestCount-1) ? 0 : active + 1;
+                        let newActive = (active === suggestCount - 1) ? 0 : active + 1;
                         changeActiveTo(newActive, suggestionElems);
                     }
                     // Up arrow
                     else if (event.keyCode === 38) {
-                        let newActive = (active === 0) ? suggestCount-1 : active - 1;
+                        let newActive = (active === 0) ? suggestCount - 1 : active - 1;
                         changeActiveTo(newActive, suggestionElems);
                     }
                     // Enter or tab key
@@ -482,6 +482,7 @@ module.exports = function (ngApp, events) {
 
                 // Display suggestions on a field
                 let prevSuggestions = [];
+
                 function displaySuggestions($input, suggestions) {
 
                     // Hide if no suggestions
@@ -518,7 +519,8 @@ module.exports = function (ngApp, events) {
                         if (i === 0) {
                             suggestion.className = 'active'
                             active = 0;
-                        };
+                        }
+                        ;
                         $suggestionBox[0].appendChild(suggestion);
                     }
 
@@ -537,17 +539,17 @@ module.exports = function (ngApp, events) {
                 // Get suggestions & cache
                 function getSuggestions(input, url) {
                     let hasQuery = url.indexOf('?') !== -1;
-                    let searchUrl = url + (hasQuery?'&':'?') + 'search=' + encodeURIComponent(input);
+                    let searchUrl = url + (hasQuery ? '&' : '?') + 'search=' + encodeURIComponent(input);
 
                     // Get from local cache if exists
-                    if (localCache[searchUrl]) {
+                    if (typeof localCache[searchUrl] !== 'undefined') {
                         return new Promise((resolve, reject) => {
-                            resolve(localCache[input]);
+                            resolve(localCache[searchUrl]);
                         });
                     }
 
-                    return $http.get(searchUrl).then((response) => {
-                        localCache[input] = response.data;
+                    return $http.get(searchUrl).then(response => {
+                        localCache[searchUrl] = response.data;
                         return response.data;
                     });
                 }
Morty Proxy This is a proxified and sanitized view of the page, visit original site.