PageSourceSearch

https://www.botlc.or.th/public/js/tag.js?v=4.0.2

js botlc.or.th collected 2026-10-02 17:36:07 UTC 55,342 bytes, 1,242 lines download raw bytes

1"use strict";
2
3riot.tag2('collapsible-content', '<div class="collapsible-wrapper {collapsed: collapsed}"><yield></yield> <div class="toggle-expand-btn" onclick="{toggle}"></div> </div> <div class="align-center" if="{is_long &amp;&amp; !toggle_selector}"><a class="link-text" href="#" onclick="{toggle}">{collapsed ? expand_label : collapse_label}</a></div>', '', 'id="{id}"', function (opts) {
4  this.id = 'collapisble-content-' + util.uniqueId();
5  this.is_long = false;
6  this["default"] = opts["default"] || 'collapsed';
7  this.height = util.cssSizeInPixel(opts.height || '24rem');
8  this.toggle_selector = opts.toggle || false;
9  this.expand_label = opts.expandLabel || 'ดูทั้งหมด';
10  this.collapse_label = opts.collapseLabel || 'ย่อขนาด';
11  this.toggle = function (e) {
12    if (e) e.preventDefault();
13    this.collapsed = !this.collapsed;
14    if (this.collapsed) {
15      this.makeCollapsed();
16    } else {
17      this.makeExpanded();
18    }
19    this.update();
20  }.bind(this);
21  this.checkNeedCollapsible = function () {
22    var content_height = $(this.root).outerHeight();
23    if (content_height > this.height) {
24      this.is_long = true;
25      this.makeCollapsed();
26    } else {
27      this.is_long = false;
28      this.makeExpanded();
29    }
30    this.update();
31  }.bind(this);
32  this.makeCollapsed = function () {
33    this.collapsed = true;
34    $(this.root).find('.collapsible-wrapper').css('max-height', this.height + 'px');
35    if (this.toggle_selector) {
36      $(this.toggle_selector).text(this.expand_label);
37    }
38  }.bind(this);
39  this.makeExpanded = function () {
40    this.collapsed = false;
41    $(this.root).find('.collapsible-wrapper').css('max-height', '');
42    if (this.toggle_selector) {
43      $(this.toggle_selector).text(this.collapse_label);
44    }
45  }.bind(this);
46  this.on('mount', function () {
47    if (this["default"] === 'collapsed') {
48      this.checkNeedCollapsible();
49    }
50    if (this.toggle_selector) {
51      var self = this;
52      $('body').on('click.' + this.id, this.toggle_selector, function (e) {
53        self.toggle();
54        self.update();
55      });
56    }
57  });
58  this.on('unmount', function () {
59    if (this.toggle_selector) {
60      $('body').off('click.' + this.id);
61    }
62  });
63});
64riot.tag2('dropdown-menu', '<div class="content"> <ul class="dropdown-list"> <li each="{data}"><a href="{url}" target="{target}" onclick="{parent.handleClick}"><i class="icon" if="{icon}">{icon}</i><span>{name}</span></a></li> </ul> </div>', '', '', function (opts) {
65  this.compact = function (array) {
66    var result = [];
67    for (var i = 0; i < array.length; i++) {
68      if (array[i]) {
69        result.push(array[i]);
70      }
71    }
72    return result;
73  };
74  this.noop = function () {};
75  this.map = function (array, iteratee) {
76    var result = [];
77    for (var i = 0; i < array.length; i++) {
78      result.push(iteratee(array[i], i));
79    }
80    return result;
81  };
82  this.merge = function (target) {
83    var sources = Array.prototype.slice.call(arguments, 1);
84    for (var i = 0; i < sources.length; i++) {
85      var source = sources[i];
86      for (var key in source) {
87        if (source.hasOwnProperty(key)) {
88          target[key] = source[key];
89        }
90      }
91    }
92    return target;
93  };
94  this.snakeCase = function (str) {
95    return str.replace(/\W+/g, ' ').replace(/([a-z\d])([A-Z])/g, '$1_$2').toLowerCase().replace(/\s+/g, '_');
96  };
97  this._target = opts.target;
98  this._content = '.content';
99  this.classes = this.compact((opts["class"] || '').split(' '));
100  this.position = 'bottom left';
101  this.menu = this.noop;
102  this.data = [];
103  this.handleClick = function (e) {
104    e.preventDefault();
105    var item = e.item;
106    if (item && typeof item.onclick === 'function') {
107      try {
108        item.onclick.call(item, e);
109      } catch (err) {
110        console.error('Dropdown click handler error:', err);
111      }
112    }
113    if (this.drop) {
114      this.drop.close();
115    }
116  }.bind(this);
117  this.setData = function (data) {
118    var self = this;
119    this.data = this.map(data, function (item, index) {
120      return self.merge({
121        id: self.snakeCase('dd-menu-item-' + util.uniqueId()),
122        name: 'Link',
123        url: '',
124        target: ''
125      }, item);
126    });
127    this.update();
128  }.bind(this);
129  this.setMenu = function (menu) {
130    if (typeof menu === 'function') {
131      this.menu = menu;
132      this.setData(this.menu());
133    }
134  }.bind(this);
135  this.on('before-mount', function () {
136    this.setMenu(opts.menu);
137    if (opts.position) {
138      this.position = opts.position;
139    }
140  });
141  this.on('mount', function () {
142    try {
143      if (typeof $ === 'undefined') {
144        console.error('jQuery is required for dropdown-menu');
145        return;
146      }
147      if (typeof Drop === 'undefined') {
148        console.error('Drop.js is required for dropdown-menu');
149        return;
150      }
151      this.$target = $(this._target);
152      this.target = this.$target.get(0);
153      this.$content = $(this.root).find(this._content);
154      this.content = this.$content.get(0);
155      if (this.target && this.content) {
156        this.drop = new Drop({
157          target: this.target,
158          content: this.content,
159          position: this.position,
160          classes: ['bb-dropdown'].concat(this.classes).join(' '),
161          constrainToWindow: true,
162          openOn: 'click',
163          remove: 'true',
164          tetherOptions: {}
165        });
166      }
167    } catch (e) {
168      console.error('Dropdown mount error:', e);
169    }
170  });
171  this.on('unmount', function () {
172    try {
173      if (this.drop) {
174        this.drop.destroy();
175      }
176    } catch (e) {
177      console.error('Dropdown unmount error:', e);
178    }
179  });
180});
181riot.tag2('image-slider', '<div class="slider-list" data-is="content"><yield></yield></div>', '', '', function (opts) {
182  this.item = opts.item || 'div';
183  this.image = opts.image !== 'false';
184  this.viewer = opts.viewer === 'true';
185  this.arrows = !(opts.arrows === 'false');
186  this.dots = opts.dots === 'true';
187  this.autoplay = !(opts.autoplay === 'false');
188  this.autoplay_speed = +(opts.autoplayspeed || 12000);
189  this.speed = +(opts.speed || 400);
190  this.fade = !(opts.fade === 'false');
191  this.extendClass = [];
192  this.extendClass = this.extendClass.join(' ');
193  this.compact = function (array) {
194    var result = [];
195    for (var i = 0; i < array.length; i++) {
196      if (array[i]) {
197        result.push(array[i]);
198      }
199    }
200    return result;
201  };
202  this.openPhotoSwipe = function () {
203    var pswpElement = $(this.$fullscreen).find('.pswp')[0];
204    var self = this;
205    var items = $(this.root).find('.slider-item:not(.slick-cloned)').map(function (index, el) {
206      var $item = $(this);
207      return {
208        src: $item.attr('data-src'),
209        w: $item.attr('data-width'),
210        h: $item.attr('data-height')
211      };
212    });
213    var options = {
214      modal: false,
215      index: this.$slider.slick('slickCurrentSlide')
216    };
217    var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
218    gallery.init();
219    gallery.listen('afterChange', function () {
220      self.$slider.slick('slickGoTo', this.getCurrentIndex(), true);
221    });
222  }.bind(this);
223  this.on('mount', function () {
224    var self = this;
225    this.$slider = $(this.root).find('.slider-list');
226    this.$slider.children().show();
227    this.$slider.on('init reinit', function (e) {
228      if (self.image) {
229        $(this).find('.slider-item').each(function () {
230          var $item = $(this);
231          var src = $item.attr('data-src') || $item.attr('data-bg-image');
232          if (!src) {
233            try {
234              var bgImageMatch = /url\(['"]?([^'"]*)['"]?\)/gi.exec($item.css('background-image'));
235              if (bgImageMatch && bgImageMatch[1]) {
236                src = bgImageMatch[1];
237              }
238            } catch (e) {
239              console.warn('Failed to extract background image URL:', e);
240            }
241          }
242          if (src) {
243            $item.empty();
244            if (self.item === 'img') {
245              $item.append($('<img/>').addClass(self.extendClass).attr('data-src', src));
246            } else {
247              $item.append($('<div class="image hide-on-error"/>').addClass(self.extendClass).attr('data-src', src).css('background-image', 'url(\'' + src + '\')'));
248            }
249          }
250        });
251      } else {
252        $(this).find('.slider-item[data-bg-image]').each(function () {
253          var $item = $(this);
254          var bgImageUrl = $item.attr('data-bg-image');
255          if (bgImageUrl) {
256            $item.css('background-image', 'url(\'' + bgImageUrl + '\')');
257          }
258        });
259      }
260    }).on('setPosition', function (e, slick) {
261      if (self.lazyload) {
262        var i = slick.currentSlide;
263        var slideIndices = [i === 0 ? slick.$slides.length - 1 : i - 1, i, i === slick.$slides.length - 1 ? 0 : i + 1];
264        var elements = self.compact($(slick.$slides[slideIndices[0]]).add(slick.$slides[slideIndices[1]]).add(slick.$slides[slideIndices[2]]
264).find('.lazyload:not(.lazyloaded)').toArray());
265      }
266    }).slick({
267      infinite: true,
268      speed: this.speed,
269      fade: this.fade,
270      autoplay: this.autoplay,
271      autoplaySpeed: this.autoplay_speed,
272      arrows: this.arrows,
273      dots: this.dots,
274      cssEase: 'ease-in'
275    });
276    if (this.viewer) {}
277  });
278  this.on('unmount', function () {
279    if (this.$slider) {
280      try {
281        if (this.$slider.hasClass('slick-initialized')) {
282          this.$slider.slick('unslick');
283        }
284        this.$slider.off('init reinit setPosition');
285      } catch (e) {
286        console.warn('Error during slider cleanup:', e);
287      }
288    }
289  });
290});
291riot.tag2('item-list-filter', '<div class="tool-container clearfix"> <div class="left"> <div class="rec-input input-field inline tool-select"> <select data-name="rec" onchange="{updateFilter}" onclick="{handleSelectChange}"> <option each="{recOptions}" riot-value="{value}" selected="{current.rec == value}">{name}</option> </select> </div> <div class="ne-input input-field inline tool-select"> <select data-name="ne" onchange="{updateFilter}" onclick="{handleSelectChange}"> <option each="{neOptions}" riot-value="{value}" selected="{current.ne == value}">{name}</option> </select> </div> <div class="year-input input-field inline tool-select"> <select data-name="y" onchange="{updateFilter}" onclick="{handleSelectChange}"> <option each="{yearOptions}" riot-value="{value}" selected="{current.y == value}">{name}</option> </select> </div> <div class="month-input input-field inline tool-select"> <select data-name="m" onchange="{updateFilter}" onclick="{handleSelectChange}"> <option each="{monthOptions}" riot-value="{value}" selected="{current.m == value}">{name}</option> </select> </div> <div class="sort-input input-field inline tool-select"> <select data-name="sort" onchange="{updateFilter}" onclick="{handleSelectChange}"> <option each="{sortOptions}" riot-value="{value}" selected="{current.sort == value}">{name}</option> </select> </div> </div> </div>', '', '', function (opts) {
292  var now = moment();
293  console.log('opts:', opts);
294  function parseQueryString(url) {
295    var params = {};
296    try {
297      if (typeof util !== 'undefined' && util.parse_querystring) {
298        return util.parse_querystring(url);
299      } else {
300        var searchParams = new URLSearchParams(window.location.search);
301        searchParams.forEach(function (value, key) {
302          params[key] = value;
303        });
304        console.log('Manually parsed query params:', params);
305        return params;
306      }
307    } catch (error) {
308      console.error('Error parsing query string:', error);
309      return {};
310    }
311  }
312  this.base_url = util.site_url(location.origin + location.pathname);
313  this.current = _.assignIn({
314    y: now.format('YYYY'),
315    m: now.format('M')
316  }, opts && opts.current || parseQueryString(location.href));
317  this.filter = opts && opts.filter || {};
318  this.monthly_archive = opts && opts.monthly_archive || false;
319  this.max_date = (opts && opts.max_date ? moment(opts.max_date) : moment()).endOf('month');
320  this.min_date = (opts && opts.min_date ? moment(opts.min_date) : moment()).startOf('month');
321  console.log('this.filter:', this.filter);
322  console.log('this.monthly_archive:', this.monthly_archive);
323  console.log('this.current (final):', this.current);
324  function do_filter() {
325    console.log('do_filter called');
326    console.log('this.current:', this.current);
327    console.log('this.base_url:', this.base_url);
328    if (this.monthly_archive) {
329      var selected_date = moment(this.current.y + '-' + this.current.m, 'YYYY-MM');
330      if (selected_date.isBefore(this.min_date)) {
331        this.current.y = this.min_date.format('YYYY');
332        this.current.m = this.min_date.format('M');
333      }
334      if (selected_date.isAfter(this.max_date)) {
335        this.current.y = this.max_date.format('YYYY');
336        this.current.m = this.max_date.format('M');
337      }
338    }
339    var queryString = '';
340    try {
341      if (typeof util !== 'undefined' && util.format_querystring) {
342        queryString = util.format_querystring(this.current);
343      } else {
344        var params = [];
345        for (var key in this.current) {
346          if (this.current[key] != null && this.current[key] !== '') {
347            params.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.current[key]));
348          }
349        }
350        queryString = params.join('&');
351      }
352      var newUrl = this.base_url + (queryString ? '?' + queryString : '');
353      console.log('Redirecting to:', newUrl);
354      window.location.href = newUrl;
355    } catch (error) {
356      console.error('Error in do_filter:', error);
357      console.log('Falling back to page reload with query params');
358      var _params = new URLSearchParams(window.location.search);
359      for (var _key in this.current) {
360        if (this.current[_key] != null && this.current[_key] !== '') {
361          _params.set(_key, this.current[_key]);
362        }
363      }
364      window.location.search = _params.toString();
365    }
366  }
367  function generateAllOptions() {
368    if (this.filter.rec && this.filter.rec.options) {
369      this.recOptions = this.filter.rec.options;
370      console.log('Rec options set:', this.recOptions.length);
371    } else {
372      this.recOptions = [];
373    }
374    if (this.filter.ne && this.filter.ne.options) {
375      this.neOptions = this.filter.ne.options;
376      console.log('Ne options set:', this.neOptions.length);
377    } else {
378      this.neOptions = [];
379    }
380    if (this.monthly_archive) {
381      var selected_year = +this.current.y || +moment().format('YYYY');
382      var min_date = this.min_date;
383      var max_date = this.max_date;
384      console.log('Date range:', min_date.format('YYYY-MM'), 'to', max_date.format('YYYY-MM'));
385      if (this.filter.y) {
386        this.filter.y.options = _.range(+min_date.format('YYYY'), +max_date.format('YYYY') + 1).map(function (year) {
387          return {
388            name: String(year),
389            value: year
390          };
391        });
392        this.filter.y.options.reverse();
393        console.log('Year options generated:', this.filter.y.options.length);
394        this.yearOptions = this.filter.y.options;
395      } else {
396        this.yearOptions = [];
397      }
398      if (this.filter.m) {
399        this.filter.m.options = _.range((selected_year === +min_date.year() ? min_date.month() : 0) + 1, (selected_year === +max_date.year() ? max_date.month() : 11) + 2).map(function (month) {
400          return {
401            name: moment(month, 'MM').format('MMMM'),
402            value: month
403          };
404        });
405        this.filter.m.options.reverse();
406        console.log('Month options generated:', this.filter.m.options.length);
407        this.monthOptions = this.filter.m.options;
408      } else {
409        this.monthOptions = [];
410      }
411    } else {
412      this.yearOptions = [];
413      this.monthOptions = [];
414    }
415    if (this.filter.sort && this.filter.sort.options) {
416      this.sortOptions = this.filter.sort.options;
417      console.log('Sort options set:', this.sortOptions.length);
418    } else {
419      this.sortOptions = [];
420    }
421  }
422  function removeEmptySelects() {
423    var _this = this;
424    console.log('Removing empty select elements...');
425    var filterChecks = [{
426      selector: '.rec-input',
427      options: this.recOptions
428    }, {
429      selector: '.ne-input',
430      options: this.neOptions
431    }, {
432      selector: '.year-input',
433      options: this.yearOptions
434    }, {
435      selector: '.month-input',
436      options: this.monthOptions
437    }, {
438      selector: '.sort-input',
439      options: this.sortOptions
440    }];
441    filterChecks.forEach(function (check) {
442      var $element = $(_this.root).find(check.selector);
443      if (!check.options || check.options.length === 0) {
444        console.log('Removing', check.selector, '- no options');
445        $element.remove();
446      } else {
447        console.log('Keeping', check.selector, '- has', check.options.length, 'options');
448      }
449    });
450  }
451  this.updateFilter = function (e) {
452    console.log('=== updateFilter called ===');
453    console.log('Event:', e);
454    console.log('Event type:', e.type);
455    console.log('Target element:', e.currentTarget);
456    var $el = $(e.currentTarget);
457    console.log('jQuery element:', $el);
458    console.log('Element data-name:', $el.data('name'));
459    console.log('Element value:', $el.val());
460    var filter_key = $el.data('name');
461    var filter_value = $el.val();
462    console.log('Filter key:', filter_key);
463    console.log('Filter value:', filter_value);
464    console.log('Current state before update:', this.current);
465    this.current[filter_key] = filter_value;
466    console.log('Current state after update:', this.current);
467    if (filter_key === 'y') {
468      console.log('Year changed, regenerating options...');
469      this.isUpdatingOptions = true;
470      generateAllOptions.call(this);
471      this.update();
472    } else {
473      console.log('Calling do_filter...');
474      do_filter.call(this);
475    }
476  }.bind(this);
477  this.handleSelectChange = function (e) {
478    console.log('=== handleSelectChange called ===');
479    console.log('Alternative event handler triggered');
480    this.updateFilter(e);
481  }.bind(this);
482  this.testYearClick = function (e) {
483    console.log('=== testYearClick called ===');
484    console.log('Riot.js event binding is working!');
485    console.log('Year select value:', $(this.root).find('select[data-name="y"]').val());
486    var yearSelect = $(this.root).find('select[data-name="y"]');
487    if (yearSelect.length) {
488      console.log('Found year select, current value:', yearSelect.val());
489      yearSelect.trigger('change');
490    }
491  }.bind(this);
492  this.on('mount', function () {
493    console.log('Component mounted');
494    generateAllOptions.call(this);
495    console.log('Options generated, calling update...');
496    this.update();
497  });
498  this.on('updated', function () {
499    var _this2 = this;
500    console.log('Component updated');
501    if (this.isUpdatingOptions) {
502      console.log('Skipping Material UI init during options update');
503      this.isUpdatingOptions = false;
504      return;
505    }
506    setTimeout(function () {
507      removeEmptySelects.call(_this2);
508      var selects = $(_this2.root).find('.tool-select select');
509      console.log('Found', selects.length, 'select elements after cleanup');
510      if (selects.length > 0) {
511        console.log('Initializing Material UI selects...');
512        selects.each(function () {
513          if ($(this).hasClass('initialized')) {
514            $(this).material_select('destroy');
515          }
516        });
517        console.log('Setting current values before Material UI init...');
518        selects.each(function (index, select) {
519          var $select = $(select);
520          var filterKey = $select.data('name');
521          var currentValue = _this2.current[filterKey];
522          console.log("Setting ".concat(filterKey, " to ").concat(currentValue));
523          if (currentValue) {
524            $select.val(currentValue);
525            $select.find('option').removeAttr('selected');
526            $select.find("option[value=\"".concat(currentValue, "\"]")).attr('selected', 'selected');
527          }
528        });
529        selects.material_select();
530        console.log('Material UI selects initialized');
531        setTimeout(function () {
532          console.log('Updating Material UI display values...');
533          selects.each(function (index, select) {
534            var $select = $(select);
535            var filterKey = $select.data('name');
536            var currentValue = _this2.current[filterKey];
537            if (currentValue) {
538              var optionText = $select.find("option[value=\"".concat(currentValue, "\"]")).text();
539              var $input = $select.siblings('.select-dropdown');
540              console.log("Setting display for ".concat(filterKey, ": ").concat(optionText));
541              $input.val(optionText);
542            }
543          });
544        }, 50);
545        console.log('Adding manual event listeners...');
546        selects.off('change.customFilter').on('change.customFilter', function (e) {
547          console.log('Manual change event triggered on:', e.target);
548          _this2.updateFilter(e);
549        });
550      } else {
551        console.log('No select elements remaining after cleanup');
552      }
553    }, 100);
554  });
555});
556riot.tag2('kc-search-box', '<form class="clearfix" id="simple-search-tool-container" if="{!advanced || !open_advanced}" onsubmit="{onSubmitSearchForm}"> <section class="simple-search-tool"> <div class="search-tool-row"> <div class="search-type-input" id="search-type-input"> <div class="select-wrapper-default"> <select class="browser-default search-dropdown-input" ref="keyword_type" name="keyword_type" onchange="{onChangeSearchType}"> <option each="{search_type}" riot-value="{value}" selected="{selected}">{name}</option> </select> </div> </div> <div class="search-keyword-input" id="search-keyword-input"> <input class="search-text-input" ref="keyword" type="search" name="keyword" riot-value="{Array.isArray(q) ? &quot;&quot; : q}" placeholder="Keyword"> </div> <div class="search-submit-button" id="search-submit-button"> <button class="btn primary btn-block" onclick="{onClickSearchButton}">Search<i class="icon material-icons">&#xe8b6;</i></button> </div> <div class="search-help-col"> <div class="left"><a class="advance-search-btn" if="{advanced}" href="{util.site_url(&quot;/knowledge/search?advance=true&quot;)}" onclick="{toggleAdvancedSearch}">Advanced Search</a></div> </div> </div> </section> </form> <form class="clearfix" id="advanced-search-tool-container" if="{advanced &amp;&amp; open_advanced}" onsubmit="{onSubmitSearchForm}"> <section class="advance-search-tool" if="{open_advanced}"> <div class="search-tool-row"> <div class="search-filter-input"> <div class="search-tool-row"> <div class="select-wrapper-default"> <select class="browser-default search-dropdown-input" ref="adv_keyword_type" name="adv[keyword_type]" onchange="{onChangeSearchType}"> <option each="{search_type}" riot-value="{value}" selected="{selected}">{name}</option> </select> </div> </div> <div class="search-tool-row"> <div class="select-wrapper-default"> <select class="browser-default search-dropdown-input" ref="adv_year_type" name="adv[year_type]" onchange="{onChangeSearchType}"> <option each="{year_type}" riot-value="{value}" selected="{selected}">{name}</option> </select> </div> </div> <div class="search-tool-row"> <div class="select-wrapper-default"> <select class="browser-default search-dropdown-input" ref="adv_lang_type" name="adv[lang_type]" onchange="{onChangeSearchType}"> <option each="{lang_type}" riot-value="{value}" selected="{selected}">{name}</option> </select> </div> </div> </div> <div class="search-compare-field-input"> <div class="search-tool-row" each="{adv_search_rows}"> <div class="search-field-input"> <div class="select-wrapper-default"> <select class="browser-default search-dropdown-input" ref="adv_q_{this}_field" name="adv[q][{this}][field]"> <option value="">Any Fields</option> <option value="title">Title</option> <option value="author">Author</option> <option value="subject">Subject</option> </select> </div> </div> <div class="search-keyword-input"> <input class="search-text-input" ref="adv_q_{this}_keyword" type="text" name="adv[q][{this}][keyword]" riot-value="{Array.isArray(q) ? (q[this] &amp;&amp; q[this].keyword ? q[this].keyword : &quot;&quot;) : &quot;&quot;}"> </div> </div> </div> </div> <div class="search-tool-row"> <div class="search-filter-input"></div> <div class="search-compare-field-input"> <div class="search-tool-row"> <div class="search-submit-button"> <div class="search-submit-button" id="advance-search-submit-button"> <button class="btn primary btn-block" onclick="{onClickSearchButton}">Search<i class="icon material-icons">&#xe8b6;</i></button> </div> </div> <div class="search-help-col"> <div class="left"><a href="{util.site_url(&quot;/knowledge/search?advanced=false&quot;)}" onclick="{toggleAdvancedSearch}">Simple Search</a></div> </div> </div> </div> </div> </section> </form>', '', '', function (opts) {
557  this.path = '/knowledge/search';
558  this.q = opts.q || '';
559  this.type = 'all';
560  this.search_type = opts.search_type || [];
561  this.year_type = opts.year_type || [];
562  this.lang_type = opts.lang_type || [];
563  this.adv_query_max = 3;
564  this.open_advanced = opts.open_advanced === true || Array.isArray(this.q) || false;
565  this.advanced = typeof opts.advanced === 'boolean' ? opts.advanced : opts.advanced === 'true' || false;
566  this.adv_search_rows = [];
567  for (var i = 0; i < this.adv_query_max; i++) {
568    this.adv_search_rows.push(i);
569  }
570  this.times = function (n) {
571    var result = [];
572    for (var i = 0; i < n; i++) {
573      result.push(i);
574    }
575    return result;
576  };
577  this.onSubmitSearchForm = function (e) {
578    e.preventDefault();
579    this.q = e.target.value;
580    var fn = typeof opts.onsubmitsearchform === 'function' ? opts.onsubmitsearchform : this.submitSearchForm;
581    fn(this.populateParams());
582  }.bind(this);
583  this.onClickSearchButton = function (e) {
584    e.preventDefault();
585    this.q = e.target.value;
586    var fn = typeof opts.onclicksearchbutton === 'function' ? opts.onclicksearchbutton : this.clickSearchButton;
587    fn(this.populateParams());
588  }.bind(this);
589  this.onChangeSearchType = function (e) {
590    e.preventDefault();
591    this.type = e.target.value;
592    var fn = typeof opts.onchangesearchtype === 'function' ? opts.onchangesearchtype : this.changeType;
593    fn(this.populateParams());
594  }.bind(this);
595  this.toggleAdvancedSearch = function (e) {
596    e.preventDefault();
597    this.open_advanced = !this.open_advanced;
598    this.update();
599  }.bind(this);
600  this.filter = function (array, predicate) {
601    var result = [];
602    for (var i = 0; i < array.length; i++) {
603      if (predicate(array[i])) {
604        result.push(array[i]);
605      }
606    }
607    return result;
608  };
609  this.map = function (array, iteratee) {
610    var result = [];
611    for (var i = 0; i < array.length; i++) {
612      result.push(iteratee(array[i], i));
613    }
614    return result;
615  };
616  this.forEach = function (collection, iteratee) {
617    if (Array.isArray(collection)) {
618      for (var i = 0; i < collection.length; i++) {
619        iteratee(collection[i], i);
620      }
621    } else {
622      for (var key in collection) {
623        if (collection.hasOwnProperty(key)) {
624          iteratee(collection[key], key);
625        }
626      }
627    }
628  };
629  this.doSearch = function () {
630    if (this.type === 'all') {
631      this.path = '/knowledge/bento';
632    }
633    var params = this.populateParams();
634    location.href = util.site_url(this.path, {
635      q: params.q,
636      type: params.filter.type
637    });
638  }.bind(this);
639  this.populateParams = function () {
640    var params = {};
641    var self = this;
642    if (this.open_advanced) {
643      var queries = [];
644      for (var index = 0; index < this.adv_query_max; index++) {
645        var keywordRef = 'adv_q_' + index + '_keyword';
646        var fieldRef = 'adv_q_' + index + '_field';
647        var keyword = this.refs[keywordRef] ? this.refs[keywordRef].value : '';
648        if (keyword) {
649          queries.push({
650            field: this.refs[fieldRef] ? this.refs[fieldRef].value : '',
651            keyword: keyword
652          });
653        }
654      }
655      params = {
656        adv: true,
657        q: queries,
658        filter: {
659          type: this.refs.adv_keyword_type ? this.refs.adv_keyword_type.value : '',
660          language: this.refs.adv_lang_type ? this.refs.adv_lang_type.value : '',
661          year: this.refs.adv_year_type ? this.refs.adv_year_type.value : ''
662        }
663      };
664      var filterMappings = [['keyword_type', 'type'], ['year_type', 'year'], ['lang_type', 'language']];
665      this.forEach(filterMappings, function (pair) {
666        self.forEach(self[pair[0]], function (item) {
667          if (item.value === params.filter[pair[1]]) {
668            item.selected = true;
669          } else {
670            item.selected = false;
671          }
672        });
673      });
674    } else {
675      params = {
676        q: this.refs.keyword ? this.refs.keyword.value : '',
677        filter: {
678          type: this.refs.keyword_type ? this.refs.keyword_type.value : ''
679        }
680      };
681    }
682    return params;
683  }.bind(this);
684  this.submitSearchForm = function (params) {
685    this.doSearch();
686  }.bind(this);
687  this.clickSearchButton = function (params) {
688    this.doSearch();
689  }.bind(this);
690  this.changeType = function (e) {}.bind(this);
691});
692riot.tag2('preloader', '<div class="preloader-wrapper active {class}"> <div class="spinner-layer spinner-blue-only"> <div class="circle-clipper left"> <div class="circle"></div> </div> <div class="gap-patch"> <div class="circle"></div> </div> <div class="circle-clipper right"> <div class="circle"></div> </div> </div> </div>', '', '', function (opts) {
693  this["class"] = opts["class"];
694});
695riot.tag2('search-box', '<a class="toggle-btn" href="#" onclick="{clickToggleSearch}"><i class="icon material-icons">&#xe8b6;</i></a> <div class="search-box-wrapper" ref="wrapper"> <form onsubmit="{submitSearch}"> <input type="search" ref="q" name="q" placeholder="Title, keyword, author" onblur="{clickToggleSearch}" tabindex="-1"> </form> </div>', '', 'class="{open: open}"', function (opts) {
696  this.open = false;
697  this.path = '/search';
698  this.clickToggleSearch = function (e) {
699    e.preventDefault();
700    this.open = !this.open;
701    $('body')[this.open ? 'addClass' : 'removeClass']('global-search-active');
702    this.update();
703  }.bind(this);
704  this.submitSearch = function (e) {
705    e.preventDefault();
706    var searchValue = this.refs.q ? this.refs.q.value : '';
707    location.href = util.site_url(this.path, {
708      q: searchValue
709    });
710  }.bind(this);
711  this.on('updated', function () {
712    if (this.open) {
713      if (this.refs.q) {
714        $(this.refs.q).focus();
715      }
716    } else {}
717  });
718});
719riot.tag2('page-error', '<div id="page-error"> <div class="container"> <div class="row"> <div class="col s12 m6 offset-m3 l4 offset-l4"> <div class="spacing-large"></div> <div class="center"><i class="icon material-icons title-icon">{icon}</i> <h4 class="center">{title}</h4> <h5>{message}</h5> </div> <div class="spacing-large"></div> </div> </div> </div> </div>', '', '', function (opts) {
720  this.title = opts.title || 'เกิดปัญหาขึ้น!';
721  this.message = opts.message || 'ลองดูใหม่นะ';
722  this.icon = opts.icon || 'info_outline';
723});
724riot.tag2('page-search', '<section class="full-container {show-sidebar: show_facet, loading: loading, sidebar-active: sidebar_active}" id="search-container"> <div class="search-result-page row no-spacing" id="search-box-row"> <kc-search-box search_type="{search_type}" advanced="{advanced_search}" open_advanced="{adv}" year_type="{year_type}" lang_type="{lang_type}" q="{q}" onchangesearchtype="{changeSearchType}" onclicksearchbutton="{clickSearchButton}" onsubmitsearchform="{submitSearchForm}"></kc-search-box> </div> <div class="row no-spacing" id="search-main-row"> <div class="col" id="search-sidebar" if="{show_facet}"> <div class="right"><a id="search-facet-sidebar-toggle-btn" href="#!" onclick="{toggleSidebar}"><i class="icon material-icons">close</i></a></div> <div class="search-sidebar-row" id="search-selected-filter" if="{hasSelectedFilters}"> <h4 class="sidebar-title">Current Filters <div class="right"><a id="clear-filter-btn" href="#" onclick="{clearFacetFilter}">Clear Filters</a></div> </h4> <div class="current-filter-group filter-group"> <div each="{selected_filters_by_group}"> <h5>{parent.getGroupKey(this)}</h5> <div class="filter-checkbox" each="{this}"> <input class="filled-in" type="checkbox" id="selected-{key}-{parent.kebabCase(value)}" name="{key}" riot-value="{value}" data-label="{name}" checked onchange="{parent.changeFacetCheckbox}"> <label for="selected-{key}-{parent.kebabCase(value)}">{name}</label> </div> </div> </div> </div> <div class="search-sidebar-row" id="search-facet" if="{selectable_facets.length &gt; 0}"> <h4 class="sidebar-title">Filter by</h4> <div each="{support_filter}"> <ul class="collapsible flat" if="{parent.hasFacetBuckets(key)}" data-collapsible="expandable"> <li> <div class="collapsible-header active">{value}</div> <div class="collapsible-body"> <div class="filter-group {&quot;show-more-item&quot;: parent.expanded_filter[key]}"> <div class="filter-checkbox {&quot;more-item&quot;: parent.getIndex(this) &gt; 10}" each="{parent.facet[key].buckets}"> <input class="filled-in" type="checkbox" id="{parent.getFilterId(key, key_)}" name="{key}" riot-value="{key_}" data-label="{name}" onchange="{parent.changeFacetCheckbox}"> <label for="{parent.getFilterId(key, key_)}">{name} ({doc_count})</label> </div> <div class="filter-more-group" if="{parent.shouldShowMoreButton(key)}"> <button class="toggle-show-more-btn" riot-value="{key}" onclick="{parent.toggleFacetExpander}">{parent.expanded_filter[key] ? \'hide\' : \'view more\'}</button> </div> </div> </div> </li> </ul> </div> </div> </div> <div class="col opaque-bg" id="search-result">
724 <div class="row search-result-header"> <div class="col s12"> <div class="spacing"></div> <h4 class="page-title"> <div class="left"><a id="search-sidebar-toggle-btn" href="#!" onclick="{toggleSidebar}"><i class="icon material-icons">filter_list</i></a></div><span if="{q}">Search:&nbsp; <div class="search-keyword-text" each="{getSearchKeywords()}">{this}</div></span><span if="{!q}">Search Results</span> </h4> </div> </div> <div class="row search-result-subheader" if="{hasResults}"> <div class="col s6"> <div class="page-subtitle search-result-meta"> <div>{getResultsRange()}</div> </div> </div> <div class="col s6 right-align"> <div class="sort-input input-field inline search-result-meta"> <select class="browser-default" onchange="{changeSearchSort}"> <option each="{sortOptions}" riot-value="{value}" selected="{value === parent.sort}">{label}</option> </select> </div> <ul class="pagination inline search-result-meta"> <li if="{pagination.prev !== false}"><a href="#page={pagination.prev}" data-page="{pagination.prev}" onclick="{clickPage}"><i class="icon material-icons">chevron_left</i></a></li> <li class="{active: this === parent.pagination.page}" each="{getPaginationRange()}"><a href="#page={this}" data-page="{this}" onclick="{parent.clickPage}">{this}</a></li> <li if="{pagination.next !== false}"><a href="#page={pagination.next}" data-page="{pagination.next}" onclick="{clickPage}"><i class="icon material-icons">chevron_right</i></a></li> </ul> </div> </div> <div class="row"> <div class="col s12"> <div id="search-result-content"> <preloader if="{loading}"></preloader> <ul class="collection collection-action" if="{items &amp;&amp; items.length &gt; 0 &amp;&amp; !loading}"> <li class="collection-item" each="{items}"> <div class="collection-img"><a href="{util &amp;&amp; util.item_url ? util.item_url(this) : &quot;#&quot;}"> <div class="image center-block" data-item-type="{meta_type}" data-bg-image="{util &amp;&amp; util.site_url &amp;&amp; thumbnail_image ? util.site_url(thumbnail_image) : &quot;&quot;}"></div></a></div> <div class="collection-main"> <div class="item-type item-subtitle">{parent.startCase(_locale_type || _type || meta_type || \'Unknown\')}</div><a class="item-title truncate" href="{util &amp;&amp; util.item_url ? util.item_url(this) : &quot;#&quot;}">{name || main_title || meta_id || \'Untitled\'}</a> <div class="meta-content"> <div class="meta meta-author" if="{display_author &amp;&amp; display_author.length &gt; 0}"><span class="label">By</span>{display_author.join(\', \')}</div> </div> <p if="{summary}">{summary}</p> </div> <div class="collection-side"> <div if="{_user_action &amp;&amp; _user_action.see_location &amp;&amp; items &amp;&amp; items.length &gt; 0}"> <div class="item-subtitle">Location</div> <ul class="item-location-list"> <li each="{parent.sortBy(parent.groupBy(items, &quot;location&quot;) || [], [&quot;location&quot;])}"><a class="item-location-callnumber modal-trigger" href="#shelf-location">{this[0].location_name} ({this[0].location})</a> <div class="item-location-name" data-location="{this[0].location}">{this[0].callnumber}</div> </li> </ul> <div class="spacing"></div> </div> <div class="item-action-list"> <div class="item-action" if="{_user_action &amp;&amp; _user_action.view_content}"><a class="btn btn-flat btn-block" href="{_user_action.view_content.url}" target="_blank">View Content</a></div> <div class="item-action" if="{_user_action &amp;&amp; _user_action.link_library}"><a class="btn btn-flat btn-block" href="{_user_action.link_library.url}" target="_blank">See Details</a></div> </div> </div> </li> </ul> <div class="empty" if="{!loading &amp;&amp; items &amp;&amp; items.length === 0 &amp;&amp; !first_time}"> <div class="spacing-large"></div> <div class="center"><i class="icon material-icons large">youtube_searched_for</i> <h4 class="center">Cannot Find What You\'re Looking For</h4> <h5>Please revise your search conditions</h5> </div> <div class="spacing-large"></div> </div> </div> </div> </div> <div class="row search-result-subheader" if="{hasResults}"> <div class="col s6"> <div class="page-subtitle search-result-meta"> <div>{getResultsRange()}</div> </div> </div> <div class="col s6 right-align"> <div class="sort-input input-field inline search-result-meta"> <select class="browser-default" onchange="{changeSearchSort}"> <option each="{sortOptions}" riot-value="{value}" selected="{value === parent.sort}">{label}</option> </select> </div> <ul class="pagination inline search-result-meta"> <li if="{pagination.prev !== false}"><a href="#page={pagination.prev}" data-page="{pagination.prev}" onclick="{clickPage}"><i class="icon material-icons">chevron_left</i></a></li>
724 <li class="{active: this === parent.pagination.page}" each="{getPaginationRange()}"><a href="#page={this}" data-page="{this}" onclick="{parent.clickPage}">{this}</a></li> <li if="{pagination.next !== false}"><a href="#page={pagination.next}" data-page="{pagination.next}" onclick="{clickPage}"><i class="icon material-icons">chevron_right</i></a></li> </ul> </div> </div> </div> </div> </section>', '', '', function (opts) {
725  this.memoize = function (fn, keyGenerator) {
726    var cache = new Map();
727    return function () {
728      var key = keyGenerator ? keyGenerator.apply(this, arguments) : JSON.stringify(arguments);
729      if (cache.has(key)) {
730        return cache.get(key);
731      }
732      var result = fn.apply(this, arguments);
733      cache.set(key, result);
734      return result;
735    };
736  };
737  this.isArray = Array.isArray;
738  this.keys = Object.keys;
739  this.forEach = function (collection, iteratee) {
740    if (Array.isArray(collection)) {
741      for (var i = 0; i < collection.length; i++) {
742        iteratee(collection[i], i);
743      }
744    } else {
745      for (var key in collection) {
746        if (collection.hasOwnProperty(key)) {
747          iteratee(collection[key], key);
748        }
749      }
750    }
751  };
752  this.map = function (array, iteratee) {
753    var result = [];
754    for (var i = 0; i < array.length; i++) {
755      result.push(iteratee(array[i], i));
756    }
757    return result;
758  };
759  this.filterArray = function (array, predicate) {
760    return array.filter(predicate);
761  };
762  this.compact = function (array) {
763    var result = [];
764    for (var i = 0; i < array.length; i++) {
765      if (array[i]) {
766        result.push(array[i]);
767      }
768    }
769    return result;
770  };
771  this.groupBy = function (array, key) {
772    return array.reduce(function (result, item) {
773      var groupKey = typeof key === 'function' ? key(item) : item[key];
774      (result[groupKey] = result[groupKey] || []).push(item);
775      return result;
776    }, {});
777  };
778  this.sortBy = function (array, keys) {
779    return array.slice().sort(function (a, b) {
780      for (var i = 0; i < keys.length; i++) {
781        var key = keys[i];
782        var aVal = a[key];
783        var bVal = b[key];
784        if (aVal < bVal) return -1;
785        if (aVal > bVal) return 1;
786      }
787      return 0;
788    });
789  };
790  this.startCase = function (str) {
791    if (!str) return '';
792    return str.replace(/\w\S*/g, function (txt) {
793      return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
794    });
795  };
796  this.kebabCase = function (str) {
797    if (!str) return '';
798    return str.replace(/\W+/g, ' ').replace(/([a-z\d])([A-Z])/g, '$1_$2').toLowerCase().replace(/\s+/g, '-');
799  };
800  this.get = function (obj, path, defaultValue) {
801    var keys = path.split('.');
802    var current = obj;
803    for (var i = 0; i < keys.length; i++) {
804      if (current == null || current[keys[i]] == null) {
805        return defaultValue;
806      }
807      current = current[keys[i]];
808    }
809    return current;
810  };
811  this.assign = Object.assign || function (target) {
812    var sources = Array.prototype.slice.call(arguments, 1);
813    sources.forEach(function (source) {
814      if (source) {
815        for (var key in source) {
816          if (source.hasOwnProperty(key)) {
817            target[key] = source[key];
818          }
819        }
820      }
821    });
822    return target;
823  };
824  this.range = function (start, stop, step) {
825    step = step || 1;
826    var result = [];
827    for (var i = start; i < stop; i += step) {
828      result.push(i);
829    }
830    return result;
831  };
832  this.zipObject = function (props, values) {
833    var result = {};
834    for (var i = 0; i < props.length; i++) {
835      result[props[i]] = values[i];
836    }
837    return result;
838  };
839  this.times = function (n, iteratee) {
840    var result = [];
841    for (var i = 0; i < n; i++) {
842      result.push(iteratee ? iteratee(i) : i);
843    }
844    return result;
845  };
846  this.flatten = function (array) {
847    var result = [];
848    for (var i = 0; i < array.length; i++) {
849      if (Array.isArray(array[i])) {
850        result = result.concat(this.flatten(array[i]));
851      } else {
852        result.push(array[i]);
853      }
854    }
855    return result;
856  };
857  this.uniq = function (array) {
858    var result = [];
859    for (var i = 0; i < array.length; i++) {
860      if (result.indexOf(array[i]) === -1) {
861        result.push(array[i]);
862      }
863    }
864    return result;
865  };
866  this.pick = function (object, keys) {
867    var result = {};
868    for (var i = 0; i < keys.length; i++) {
869      var key = keys[i];
870      if (object.hasOwnProperty(key)) {
871        result[key] = object[key];
872      }
873    }
874    return result;
875  };
876  this.omit = function (object, keys) {
877    var result = {};
878    for (var key in object) {
879      if (object.hasOwnProperty(key) && keys.indexOf(key) === -1) {
880        result[key] = object[key];
881      }
882    }
883    return result;
884  };
885  this.initializeState = function () {
886    this.base_url = opts.base_url || 'search';
887    this.first_time = true;
888    this.loading = false;
889    this.sidebar_active = false;
890    this.show_facet = !!opts.facet;
891    this.page_range = 3;
892    this.items = [];
893    this.q = opts.q || '';
894    this.adv = opts.adv === true || Array.isArray(this.q);
895    this.type = opts.type || 'all';
896    this.filters = opts.filter || {};
897    this.facet = {};
898    this.sort = opts.sort || 'score';
899    this.pagination = {
900      page: 1,
901      next: false,
902      prev: false,
903      min_page: 1,
904      max_page: 1,
905      limit: 10,
906      total: 0
907    };
908    this.advanced_search = opts.advanced_search || false;
909    this.support_filter = opts.support_filter_name || {};
910    this.selected_filters_by_group = {};
911    this.selectable_facets = [];
912    this.search_type = opts.search_type || [];
913    this.year_type = opts.year_type || [];
914    this.lang_type = opts.lang_type || [];
915    var filter_keys = Object.keys(this.support_filter);
916    this.expanded_filter = this.zipObject(filter_keys, this.times(filter_keys.length, function () {
917      return false;
918    }));
919    this.sortOptions = [{
920      value: 'score',
921      label: 'Relevance'
922    }, {
923      value: '-date',
924      label: 'Latest'
925    }, {
926      value: 'date',
927      label: 'Oldest'
928    }, {
929      value: 'name',
930      label: 'A - Z'
931    }, {
932      value: '-name',
933      label: 'Z - A'
934    }];
935  };
936  this.hasSelectedFilters = function () {
937    return Object.keys(this.selected_filters_by_group).length > 0;
938  };
939  this.hasResults = function () {
940    return this.items && this.items.length > 0;
941  };
942  this.getSearchKeywords = function () {
943    return Array.isArray(this.q) ? this.q : [this.q];
944  };
945  this.getResultsRange = function () {
946    var start = this.pagination.limit * (this.pagination.page - 1) + 1;
947    var end = Math.min(this.pagination.total, this.pagination.limit * this.pagination.page);
948    return start + ' - ' + end + ' of ' + this.pagination.total + ' results';
949  };
950  this.getPaginationRange = function () {
951    return this.range(this.pagination.min_page, this.pagination.max_page + 1);
952  };
953  this.hasFacetBuckets = function (key) {
954    return this.get(this.facet, key + ".buckets.length", 0) > 0;
955  };
956  this.shouldShowMoreButton = function (key) {
957    return this.facet[key] && this.facet[key].buckets && this.facet[key].buckets.length > 10;
958  };
959  this.getGroupKey = function (group) {
960    for (var key in this.selected_filters_by_group) {
961      if (this.selected_filters_by_group[key] === group) {
962        return key;
963      }
964    }
965    return '';
966  };
967  this.getIndex = function (item) {
968    var parent = this.parent;
969    var array = parent.facet[parent.key].buckets;
970    return array.indexOf(item);
971  };
972  this.getFilterId = function (key, value) {
973    return key + '-' + this.kebabCase(value);
974  };
975  this.hasAuthors = function () {
976    return this.display_author && this.display_author.length > 0;
977  };
978  this.hasLocationItems = function () {
979    return this._user_action && this._user_action.see_location && this.items && this.items.length > 0;
980  };
981  this.getLocationGroups = function () {
982    if (!this.items || !this.items.length) return [];
983    return this.sortBy(this.groupBy(this.items, "location") || [], ["location"]);
984  };
985  this.updateParams = function (params) {
986    if (typeof params.q !== 'undefined') {
987      this.q = params.q;
988      this.adv = Array.isArray(this.q);
989    }
990    if (params.filter) {
991      if (typeof params.filter.type !== 'undefined') {
992        this.type = params.filter.type;
993      }
994      if (typeof params.filter.language !== 'undefined') {
995        this.filters.language = params.filter.language;
996      }
997      if (typeof params.filter.year !== 'undefined') {
998        this.filters.year = params.filter.year;
999      }
1000    }
1001  };
1002  this.makeSearchQuery = function () {
1003    var filter = {};
1004    var display_filter = {};
1005    var self = this;
1006    this.forEach(this.keys(this.support_filter), function (key) {
1007      if (self.filters[key]) {
1008        var values = self.map(self.filters[key].split(','), function (value) {
1009          var paths = value.split(':');
1010          return paths[0];
1011        });
1012        filter[key] = values.join(',');
1013        display_filter[key] = self.filters[key];
1014      }
1015    });
1016    var searchType = this.type;
1017    if (searchType === "all") {
1018      searchType = "library";
1019    }
1020    var params = {
1021      q: this.q,
1022      type: searchType,
1023      page: this.pagination.page,
1024      sort: this.sort || 'score',
1025      facet: this.show_facet,
1026      adv: this.adv
1027    };
1028    console.log('makeSearchQuery params:', params);
1029    return {
1030      value: this.assign({}, params, filter),
1031      display: this.assign({}, params, display_filter)
1032    };
1033  };
1034  this.parseItem = function (item) {
1035    console.log('parseItem called with:', item);
1036    var parsedItem = {};
1037    for (var key in item) {
1038      if (item.hasOwnProperty(key)) {
1039        parsedItem[key] = item[key];
1040      }
1041    }
1042    if (item.content && !parsedItem.summary) {
1043      try {
1044        if (typeof util !== 'undefined' && util.strip_tags) {
1045          parsedItem.summary = util.strip_tags(item.content);
1046        } else {
1047          parsedItem.summary = item.content.replace(/<[^>]*>/g, '');
1048        }
1049        if (parsedItem.summary.length > 240) {
1050          parsedItem.summary = parsedItem.summary.slice(0, 240) + '...';
1051        }
1052      } catch (e) {
1053        console.error('Error processing summary:', e);
1054        parsedItem.summary = item.content;
1055      }
1056    }
1057    if (!parsedItem.summary && item.summary) {
1058      parsedItem.summary = item.summary;
1059    }
1060    console.log('parseItem result:', parsedItem);
1061    console.log('parseItem result keys:', Object.keys(parsedItem));
1062    return parsedItem;
1063  };
1064  this.doSearch = function () {
1065    var self = this;
1066    this.loading = true;
1067    this.update();
1068    var search_query = this.makeSearchQuery();
1069    console.log('DoSearch called with query:', search_query);
1070    riot.route(this.base_url + '?' + util.format_querystring(util.make_querystring(search_query.display)));
1071    fetch(util.site_url('/search', app.config.api_url, search_query.value)).then(function (response) {
1072      console.log('API Response status:', response.status);
1073      if (!response.ok) {
1074        throw new Error('Network response was not ok: ' + response.status);
1075      }
1076      return response.json();
1077    }).then(function (data) {
1078      console.log('API success, data received:', data);
1079      self.processSearchData(data);
1080    })["catch"](function (err) {
1081      console.error('Search error:', err);
1082      self.handleSearchError(err);
1083    });
1084  };
1085  this.processSearchData = function (data) {
1086    var self = this;
1087    console.log('processSearchData called with:', data);
1088    console.log('Raw data.data:', data.data);
1089    this.items = this.map(data.data || [], function (item) {
1090      return self.parseItem(item);
1091    });
1092    console.log('Processed items:', this.items);
1093    console.log('Items length:', this.items.length);
1094    this.q = data.search ? data.search.q : this.q;
1095    this.adv = Array.isArray(this.q);
1096    this.pagination = data.pagination || this.pagination;
1097    this.facet = data.facet || {};
1098    console.log('State after processing:', {
1099      items: this.items.length,
1100      pagination: this.pagination,
1101      loading: this.loading,
1102      first_time: this.first_time
1103    });
1104    this.loading = false;
1105    this.first_time = false;
1106    this.update();
1107    console.log('Update called, final state:', {
1108      items: this.items.length,
1109      loading: this.loading,
1110      first_time: this.first_time,
1111      hasResults: this.hasResults()
1112    });
1113  };
1114  this.handleSearchError = function (error) {
1115    this.loading = false;
1116    this.items = [];
1117    this.pagination.total = 0;
1118    this.update();
1119  };
1120  this.updateData = function (data) {
1121    var self = this;
1122    var selected_filters = [];
1123    this.forEach(this.filters, function (value, key) {
1124      if (!value) return;
1125      var vals = value.split(',');
1126      self.forEach(vals, function (val) {
1127        var paths = val.split(':');
1128        selected_filters.push({
1129          key: key,
1130          title: self.support_filter[key],
1131          value: decodeURIComponent(paths[0]),
1132          name: decodeURIComponent(paths[1] || self.startCase(val))
1133        });
1134      });
1135    });
1136    var omitTypeFilter = function omitTypeFilter(filter) {
1137      return filter.key !== 'type';
1138    };
1139    this.selected_filters_by_group = this.groupBy(this.filterArray(selected_filters, omitTypeFilter), 'title');
1140    var validFacetKeys = [];
1141    for (var key in this.facet) {
1142      if (this.facet.hasOwnProperty(key) && this.facet[key] && this.facet[key].buckets && this.facet[key].buckets.length > 0) {
1143        validFacetKeys.push(key);
1144      }
1145    }
1146    this.selectable_facets = validFacetKeys;
1147    this.pagination.min_page = Math.max(1, this.pagination.page - this.page_range);
1148    this.pagination.max_page = Math.min(Math.ceil(this.pagination.total / this.pagination.limit), this.pagination.page + this.page_range);
1149  };
1150  this.initializeState();
1151  this.submitSearchForm = function (params) {
1152    this.updateParams(params);
1153    this.pagination.page = 1;
1154    this.doSearch();
1155  }.bind(this);
1156  this.clickSearchButton = function (params) {
1157    this.updateParams(params);
1158    this.pagination.page = 1;
1159    this.doSearch();
1160  }.bind(this);
1161  this.clickPage = function (e) {
1162    e.preventDefault();
1163    this.pagination.page = +e.currentTarget.dataset.page;
1164    this.doSearch();
1165  }.bind(this);
1166  this.changeSearchType = function (params) {
1167    this.updateParams(params);
1168    this.pagination.page = 1;
1169    this.doSearch();
1170  }.bind(this);
1171  this.changeSearchSort = function (e) {
1172    this.sort = e.currentTarget.value;
1173    this.pagination.page = 1;
1174    this.doSearch();
1175  }.bind(this);
1176  this.changeFacetCheckbox = function (e) {
1177    var filter_key = e.currentTarget.name;
1178    var $checked = $('input[name="' + filter_key + '"]:checked');
1179    var value = [];
1180    var self = this;
1181    $checked.each(function (i, el) {
1182      value.push(encodeURIComponent(el.value) + ':' + encodeURIComponent($(el).attr('data-label')));
1183    });
1184    if (value.length > 0) {
1185      this.filters[filter_key] = value.join(',');
1186    } else {
1187      delete this.filters[filter_key];
1188    }
1189    this.pagination.page = 1;
1190    this.doSearch();
1191  }.bind(this);
1192  this.clearFacetFilter = function (e) {
1193    e.preventDefault();
1194    this.filters = {};
1195    this.pagination.page = 1;
1196    this.doSearch();
1197  }.bind(this);
1198  this.toggleFacetExpander = function (e) {
1199    e.preventDefault();
1200    var filter_key = e.currentTarget.value;
1201    this.expanded_filter[filter_key] = !this.expanded_filter[filter_key];
1202  }.bind(this);
1203  this.toggleSidebar = function (e) {
1204    e.preventDefault();
1205    this.sidebar_active = !this.sidebar_active;
1206  }.bind(this);
1207  this.on('mount', function () {
1208    try {
1209      if (this.tags["kc-search-box"] && this.tags["kc-search-box"].populateParams) {
1210        this.updateParams(this.tags["kc-search-box"].populateParams());
1211      }
1212      if (this.type === "all") {
1213        this.type = "library";
1214      }
1215      this.pagination.page = 1;
1216      if (this.q || this.show_facet) {
1217        this.doSearch();
1218      } else {
1219        console.log('No initial query, waiting for user input...');
1220        this.first_time = false;
1221        this.loading = false;
1222        this.update();
1223      }
1224    } catch (e) {
1225      console.error('Mount error:', e);
1226    }
1227  }.bind(this));
1228  this.on('updated', function () {
1229    try {
1230      this.updateData();
1231      if (typeof $ !== 'undefined') {
1232        $(this.root).find('.modal-trigger').leanModal();
1233        $(this.root).find('.collapsible').collapsible();
1234      }
1235      if (typeof app !== 'undefined' && app.check_image) {
1236        app.check_image();
1237      }
1238    } catch (e) {
1239      console.error('Update error:', e);
1240    }
1241  }.bind(this));
1242});

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.