1humhub.module('ui.search', function(module, require, $) { 2 const client = require('client'); 3 const loader = require('ui.loader'); 4 const Widget = require('ui.widget').Widget; 5 const highlightWords = require('ui.additions').highlightWords; 6 7 const Search = Widget.extend(); 8 9 Search.prototype.init = function() { 10 const that = this; 11 12 that.selectors = { 13 toggler: '#search-menu[data-bs-toggle=dropdown]', 14 panel: '#dropdown-search', 15 close: '#dropdown-search-close', 16 list: '.dropdown-search-list', 17 form: '.dropdown-search-form', 18 input: 'input.dropdown-search-keyword', 19 provider: '.search-provider', 20 providerSearched: '.search-provider.provider-searched', 21 providerContent: '.search-provider-content', 22 providerRecord: '.search-provider-record', 23 providerRecordText: '.search-provider-record-text', 24 providerCounter: '.search-provider-title > span', 25 providerShowAll: '.search-provider-show-all', 26 backdrop: '.dropdown-backdrop', 27 additionalToggler: { 28 form: 'form[data-bs-toggle="humhub.ui.search"]', 29 input: 'input[type=text]:first', 30 submit: '[type=submit]' 31 } 32 } 33 34 $(document).on('pjax:end', function () { 35 that.reset(); 36 }); 37 38 $(document).on('click', that.selectors.panel, function (e) { 39 e.stopPropagation(); 40 }); 41 42 $(document).on('click', that.selectors.close + ', ' 43 + that.selectors.providerRecord + ', ' 44 + that.selectors.providerShowAll, function () { 45 that.getBsMenuDropdown().hide(); 46 }); 47 48 that.getInput().on('keypress', function (e) { 49 if (e.which === 13) { 50 that.search(); 51 } 52 }).on('keyup', function () { 53 that.searchTimeout(() => that.search(true)); 54 }).on('keydown', function (e) { 55 return that.switchFocus(e.currentTarget.tagName, e.which); 56 }); 57 58 that.getList().on('keypress', function () { 59 that.getCurrentInput().focus(); 60 }).on('keydown', function (e) { 61 return that.switchFocus(e.currentTarget.tagName, e.which); 62 }); 63 64 that.getList().niceScroll({ 65 cursorwidth: '7', 66 cursorborder: '', 67 cursorcolor: '#555', 68 cursoropacitymax: '0.2', 69 nativeparentscrolling: false, 70 railpadding: {top: 0, right: 0, left: 0, bottom: 0} 71 }); 72 73 that.$.on('shown.bs.dropdown', function () { 74 if (that.getBackdrop().length === 0) { 75 // Add the backdrop 76 that.$.append('<div class="' + that.selectors.backdrop.replace('.', '') + '">').promise().done(function() { 77 // Close if the backdrop is clicked 78 that.getBackdrop().on('click', function () { 79 that.getBsMenuDropdown().hide(); 80 }); 81 }); 82 } 83 if (that.getList().is(':visible')) { 84 // refresh NiceScroll after reopen it with searched results 85 that.getList().hide().show(); 86 } 87 if (that.getInput().is(':visible')) { 88 that.getInput().focus(); 89 } 90 }) 91 92 const additionalTogglerForm = $(that.selectors.additionalToggler.form); 93 const additionalTogglerInput = additionalTogglerForm.length ? additionalTogglerForm.find(that.selectors.additionalToggler.input) : null; 94 95 that.$.on('hide.bs.dropdown', function (e) { 96 if (!additionalTogglerInput || !additionalTogglerInput.is(':focus') || additionalTogglerInput.val().trim() === '') { 97 that.getBackdrop().remove(); 98 } 99 }) 100 101 if (additionalTogglerInput) { 102 that.initAdditionalToggle(additionalTogglerForm, additionalTogglerInput); 103 } 104 } 105 106 Search.prototype.initAdditionalToggle = function (form, input) { 107 const that = this; 108 const submit = form.find(that.selectors.additionalToggler.submit); 109 110 const search = function (keyword, forceCurrentSearching) { 111 that.getForm().hide();
112 that.getInput().val(keyword); 113 that.showPanel().search(forceCurrentSearching); 114 } 115 116 submit.on('click', function () { 117 search(input.val()); 118 return false; 119 }); 120 121 input.on('keypress', function (e) { 122 if (e.which === 13) { 123 e.preventDefault(); 124 search($(this).val()); 125 } 126 }).on('keyup', function () { 127 that.searchTimeout(() => search($(this).val(), true)); 128 }).on('keydown', function (e) { 129 return that.switchFocus(e.currentTarget.tagName, e.which); 130 }); 131 } 132 133 Search.prototype.getMenuToggler = function () { 134 return this.$.find(this.selectors.toggler); 135 } 136 137 Search.prototype.getBsMenuDropdown = function () { 138 return new bootstrap.Dropdown(this.getMenuToggler().parent('.dropdown')); 139 } 140 141 Search.prototype.getBackdrop = function () { 142 return this.$.find(this.selectors.backdrop); 143 } 144 145 Search.prototype.getList = function () { 146 return this.$.find(this.selectors.list); 147 } 148 149 Search.prototype.getProviders = function () { 150 return this.$.find(this.selectors.provider); 151 } 152 153 Search.prototype.getForm = function () { 154 return this.$.find(this.selectors.form); 155 } 156 157 Search.prototype.getInput = function () { 158 return this.$.find(this.selectors.input); 159 } 160 161 Search.prototype.hasInput = function () { 162 const input = this.getInput(); 163 return input.length === 1 && input.is(':visible'); 164 } 165 166 Search.prototype.getAdditionalInput = function () { 167 return $(this.selectors.additionalToggler.form + ' ' + this.selectors.additionalToggler.input); 168 } 169 170 Search.prototype.hasAdditionalInput = function (isVisible) { 171 const input = this.getAdditionalInput(); 172 if (input.length === 0) { 173 return false; 174 } 175 return typeof isVisible === 'undefined' || isVisible ? input.is(':visible') : !input.is(':visible'); 176 } 177 178 Search.prototype.getCurrentInput = function () { 179 return this.hasAdditionalInput() ? this.getAdditionalInput() : this.getInput(); 180 } 181 182 Search.prototype.isVisiblePanel = function () { 183 return $(this.selectors.toggler).hasClass('show'); 184 } 185 186 Search.prototype.showPanel = function () { 187 if (!this.isVisiblePanel()) { 188 this.getBsMenuDropdown().show(); 189 } 190 return this; 191 } 192 193 Search.prototype.hidePanel = function () { 194 if (this.isVisiblePanel()) { 195 this.getBsMenuDropdown().hide(); 196 } 197 return this; 198 } 199 200 Search.prototype.isSearched = function () { 201 return this.$.find(this.selectors.providerSearched).length > 0; 202 } 203 204 Search.prototype.menu = function () { 205 this.getForm().show(); 206 } 207 208 Search.prototype.searchTimeout = function (searchFunction) { 209 const that = this; 210 211 if (typeof that.currentSearchTimeout !== 'undefined') { 212 clearTimeout(that.currentSearchTimeout); 213 } 214 215 that.currentSearchTimeout = setTimeout(function () { 216 that.currentSearchTimeout = undefined; 217 searchFunction(); 218 }, 500); 219 220 // Run this only to display a search loader immediately, 221 // The real search process will be rejected while currentSearchTimeout is set 222 searchFunction(); 223 } 224 225 Search.prototype.search = function (forceCurrentSearching) { 226 const that = this; 227 const data = { 228 provider: null, 229 keyword: that.getInput().val().trim() 230 }; 231 232 if (data.keyword === '') { 233 that.getList().hide(); 234 that.getInput().val(''); 235 that.previousKeyword = ''; 236 if (!that.hasInput()) { 237 that.hidePanel(); 238 } 239 return; 240 } 241 242 if (that.previousKeyword === data.keyword) { 243 this.getProviders().each(function () { 244 const provider = $(this); 245 provider.removeClass('provider-searching'); 246 loader.reset(provider.find(that.selectors.providerContent)); 247 }); 248 return; 249 } 250 251 if (that.hasAdditionalInput(false)) { 252 that.getAdditionalInput().val(data.keyword); 253 } 254 255 this.getList().show(); 256 257 this.getProviders().each(function () { 258 const provider = $(this); 259 260 if (!forceCurrentSearching && provider.hasClass('provider-searching')) { 261 return; 262 } 263 264 provider.addClass('provider-searching').show() 265 .find(that.selectors.providerCounter).hide(); 266 loader.set(provider.find(that.selectors.providerContent), {size: '8px', css: {padding: '0px'}}); 267 268 if (typeof that.currentSearchTimeout !== 'undefined') { 269 // Don't run the search process while time is not expired, 270 // This action was called only to display the search loader 271 return; 272 } 273 274 data.provider = provider.data('provider'); 275 data.route = provider.data('provider-route'); 276 client.post(module.config.url, {data}).then(function (response) {
277 if (data.keyword !== that.getInput().val().trim()) { 278 // Skip this request because other with new keyword was sent 279 return; 280 } 281 282 // Prepare and set new content 283 const newProviderContent = $(response.html); 284 provider.replaceWith(newProviderContent); 285 const records = newProviderContent.find(that.selectors.providerRecord); 286 if (records.length) { 287 highlightWords(records.find(that.selectors.providerRecordText), data.keyword); 288 } else if (newProviderContent.data('hide-on-empty') !== undefined) { 289 newProviderContent.hide(); 290 } 291 }).catch(function (e) { 292 module.log.error(e, true); 293 loader.reset(provider.find(that.selectors.providerContent)); 294 provider.hide(); 295 }); 296 297 that.previousKeyword = data.keyword; 298 }); 299 } 300 301 Search.prototype.reset = function () { 302 this.getCurrentInput().val(''); 303 this.getProviders().hide(); 304 this.hidePanel(); 305 } 306 307 Search.prototype.switchFocus = function (tag, key) { 308 if (key !== 38 && key !== 40) { 309 return true; 310 } 311 312 const dir = key === 38 ? 'up' : 'down'; 313 const links = this.getList().find('a:visible'); 314 315 if (tag === 'INPUT') { 316 if (dir === 'down') { 317 links.first().focus(); 318 return false; 319 } else if (dir === 'up') { 320 links.last().focus(); 321 return false; 322 } 323 } else if (tag === 'UL') { 324 if ((dir === 'down' && links.last().is(':focus')) || 325 (dir === 'up' && links.first().is(':focus'))) { 326 this.getCurrentInput().focus(); 327 return false; 328 } 329 } 330 331 return true; 332 } 333 334 module.export = Search; 335});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.