PageSourceSearch

https://kaizynx.github.io/js/InsightSearch.js

js kaizynx.github.io collected 2026-10-03 09:26:31 UTC 9,143 bytes, 243 lines download raw bytes

1/**
2 * Insight search plugin
3 * @author PPOffice { @link https://github.com/ppoffice }
4 */
5(function ($, CONFIG) {
6    var $main = $('.ins-search');
7    var $input = $main.find('.ins-search-input');
8    var $wrapper = $main.find('.ins-section-wrapper');
9    var $container = $main.find('.ins-section-container');
10    $main.parent().remove('.ins-search');
11    $('body').append($main);
12
13    function section (title) {
14        return $('<section>').addClass('ins-section')
15            .append($('<header>').addClass('ins-section-header').text(title));
16    }
17
18    function searchItem (icon, title, slug, preview, url) {
19        return $('<div>').addClass('ins-selectable').addClass('ins-search-item')
20            .append($('<header>').append($('<i>').addClass('fa').addClass('fa-' + icon)).append(title != null && title != '' ? title : CONFIG.TRANSLATION['UNTITLED'])
21                .append(slug ? $('<span>').addClass('ins-slug').text(slug) : null))
22            .append(preview ? $('<p>').addClass('ins-search-preview').text(preview) : null)
23            .attr('data-url', url);
24    }
25
26    function sectionFactory (type, array) {
27        var sectionTitle;
28        var $searchItems;
29        if (array.length === 0) return null;
30        sectionTitle = CONFIG.TRANSLATION[type];
31        switch (type) {
32            case 'POSTS':
33            case 'PAGES':
34                $searchItems = array.map(function (item) {
35                    // Use config.root instead of permalink to fix url issue
36                    return searchItem('file', item.title, null, item.text.slice(0, 150), CONFIG.ROOT_URL + item.path);
37                });
38                break;
39            case 'CATEGORIES':
40            case 'TAGS':
41                $searchItems = array.map(function (item) {
42                    return searchItem(type === 'CATEGORIES' ? 'folder' : 'tag', item.name, item.slug, null, item.permalink);
43                });
44                break;
45            default:
46                return null;
47        }
48        return section(sectionTitle).append($searchItems);
49    }
50
51    function extractToSet (json, key) {
52        var values = {};
53        var entries = json.pages.concat(json.posts);
54        entries.forEach(function (entry) {
55            if (entry[key]) {
56                entry[key].forEach(function (value) {
57                    values[value.name] = value;
58                });
59            }
60        });
61        var result = [];
62        for (var key in values) {
63            result.push(values[key]);
64        }
65        return result;
66    }
67
68    function parseKeywords (keywords) {
69        return keywords.split(' ').filter(function (keyword) {
70            return !!keyword;
71        }).map(function (keyword) {
72            return keyword.toUpperCase();
73        });
74    }
75
76    /**
77     * Judge if a given post/page/category/tag contains all of the keywords.
78     * @param Object            obj     Object to be weighted
79     * @param Array<String>     fields  Object's fields to find matches
80     */
81    function filter (keywords, obj, fields) {
82        var result = false;
83        var keywordArray = parseKeywords(keywords);
84        var containKeywords = keywordArray.filter(function (keyword) {
85            var containFields = fields.filter(function (field) {
86                if (!obj.hasOwnProperty(field))
87                    return false;
88                if (obj[field].toUpperCase().indexOf(keyword) > -1)
89                    return true;
90            });
91            if (containFields.length > 0)
92                return true;
93            return false;
94        });
95        return containKeywords.length === keywordArray.length;
96    }
97
98    function filterFactory (keywords) {
99        return {
100            POST: function (obj) {
101                return filter(keywords, obj, ['title', 'text']);
102            },
103            PAGE: function (obj) {
104                return filter(keywords, obj, ['title', 'text']);
105            },
106            CATEGORY: function (obj) {
107                return filter(keywords, obj, ['name', 'slug']);
108            },
109            TAG: function (obj) {
110                return filter(keywords, obj, ['name', 'slug']);
111            }
112        };
113    }
114
115    /**
116     * Calculate the weight of a matched post/page/category/tag.
117     * @param Object            obj     Object to be weighted
118     * @param Array<String>     fields  Object's fields to find matches
119     * @param Array<Integer>    weights Weight of every field
120     */
121    function weight (keywords, obj, fields, weights) {
122        var value = 0;
123        parseKeywords(keywords).forEach(function (keyword) {
124            var pattern = new RegExp(keyword, 'img'); // Global, Multi-line, Case-insensitive
125            fields.forEach(function (field, index) {
126                if (obj.hasOwnProperty(field)) {
127                    var matches = obj[field].match(pattern);
128                    value += matches ? matches.length * weights[index] : 0;
129                }
130            });
131        });
132        return value;
133    }
134
135    function weightFactory (keywords) {
136        return {
137            POST: function (obj) {
138                return weight(keywords, obj, ['title', 'text'], [3, 1]);
139            },
140            PAGE: function (obj) {
141                return weight(keywords, obj, ['title', 'text'], [3, 1]);
142            },
143            CATEGORY: function (obj) {
144                return weight(keywords, obj, ['name', 'slug'], [1, 1]);
145            },
146            TAG: function (obj) {
147                return weight(keywords, obj, ['name', 'slug'], [1, 1]);
148            }
149        };
150    }
151
152    function search (json, keywords) {
153        var WEIGHTS = weightFactory(keywords);
154        var FILTERS = filterFactory(keywords);
155        var posts = json.posts;
156        // var pages = json.pages;
157        var tags = extractToSet(json, 'tags');
158        var categories = extractToSet(json, 'categories');
159        return {
160            posts: posts.filter(FILTERS.POST).sort(function (a, b) { return WEIGHTS.POST(b) - WEIGHTS.POST(a); }).slice(0, 20),
161            // pages: pages.filter(FILTERS.PAGE).sort(function (a, b) { return WEIGHTS.PAGE(b) - WEIGHTS.PAGE(a); }).slice(0, 5),
162            categories: categories.filter(FILTERS.CATEGORY).sort(function (a, b) { return WEIGHTS.CATEGORY(b) - WEIGHTS.CATEGORY(a); }).slice(0, 5),
163            tags: tags.filter(FILTERS.TAG).sort(function (a, b) { return WEIGHTS.TAG(b) - WEIGHTS.TAG(a); }).slice(0, 5)
164        };
165    }
166
167    function searchResultToDOM (searchResult) {
168        $container.empty();
169        for (var key in searchResult) {
170            $container.append(sectionFactory(key.toUpperCase(), searchResult[key]));
171        }
172    }
173
174    function scrollTo ($item) {
175        if ($item.length === 0) return;
176        var wrapperHeight = $wrapper[0].clientHeight;
177        var itemTop = $item.position().top - $wrapper.scrollTop();
178        var itemBottom = $item[0].clientHeight + $item.position().top;
179        if (itemBottom > wrapperHeight + $wrapper.scrollTop()) {
180            $wrapper.scrollTop(itemBottom - $wrapper[0].clientHeight);
181        }
182        if (itemTop < 0) {
183            $wrapper.scrollTop($item.position().top);
184        }
185    }
186
187    function selectItemByDiff (value) {
188        var $items = $.makeArray($container.find('.ins-selectable'));
189        var prevPosition = -1;
190        $items.forEach(function (item, index) {
191            if ($(item).hasClass('active')) {
192                prevPosition = index;
193                return;
194            }
195        });
196        var nextPosition = ($items.length + prevPosition + value) % $items.length;
197        $($items[prevPosition]).removeClass('active');
198        $($items[nextPosition]).addClass('active');
199        scrollTo($($items[nextPosition]));
200    }
201
202    function gotoLink ($item) {
203        if ($item && $item.length) {
204            location.href = $item.attr('data-url');
205        }
206    }
207
208    $.getJSON(CONFIG.CONTENT_URL, function (json) {
209        if (location.hash.trim() === '#ins-search') {
210            $main.addClass('show');
211        }
212        $input.on('input', function () {
213            var keywords = $(this).val();
214            searchResultToDOM(search(json, keywords));
215        });
216        $input.trigger('input');
217    });
218
219
220    $(document).on('click focus', '.search-field', function () {
221        $main.addClass('show');
222        $main.find('.ins-search-input').focus();
223    }).on('click focus', '.search-form-submit', function () {
224        $main.addClass('show');
225        $main.find('.ins-search-input').focus();
226    }).on('click', '.ins-search-item', function () {
227        gotoLink($(this));
228    }).on('click', '.ins-close', function () {
229        $main.removeClass('show');
230    }).on('keydown', function (e) {
231        if (!$main.hasClass('show')) return;
232        switch (e.keyCode) {
233            case 27: // ESC
234                $main.removeClass('show'); break;
235            case 38: // UP
236                selectItemByDiff(-1); break;
237            case 40: // DOWN
238                selectItemByDiff(1); break;
239            case 13: //ENTER
240                gotoLink($container.find('.ins-selectable.active').eq(0)); break;
241        }
242    });
243})(jQuery, window.INSIGHT_CONFIG);

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.