jekyll-client-search 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +1 -0
- data/assets/client-search-base.js +4 -44
- data/assets/client-search-dropdown.js +3 -35
- data/assets/client-search-related.js +1 -1
- data/assets/client-search-shared.js +57 -0
- data/assets/query-embedders/transformers-worker.js +3 -0
- data/lib/jekyll/client_search/configuration.rb +1 -1
- data/lib/jekyll/client_search/dropdown_tag.rb +19 -13
- data/lib/jekyll/client_search/search_tag.rb +1 -0
- data/lib/jekyll/client_search/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 74262c7813a4c946b9a4c9c7d7a19aa1d866dedfdf82781131e5edbe9aabbdef
|
|
4
|
+
data.tar.gz: 6ebbd6018cfc419d586d50d60d7d346c5a6c0a7b442275caf55fc22d3420a797
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 402221144b4bbf95e1daf759430c6a59fad8025d650f4f84624146ed44951de2e34755a1e335f478aeabe2fbec693f2b95c74c64aad1ff19e54b3a1e30213ee8
|
|
7
|
+
data.tar.gz: 84d405d332fdd45895885b43dc408a46574b97f26f2b1742513ad97f9910e0ed96f6bb4ddff0c4cf0148751b2a0348b97b5adaa41aab57ba5c724c07f0827d5e
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.4 — 2026-09-11
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- SonarQube Cloud badge in README.md (project key: `gundestrup_jekyll-client-search`)
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Verify `event.origin` in transformers-worker.js `message` listener to prevent cross-origin message injection (SonarQube: `javascript:S2819`, CRITICAL)
|
|
12
|
+
- Add `--ignore-scripts` to `npm ci` in CI to prevent lifecycle script execution during dependency installation (SonarQube: `githubactions:S6505`)
|
|
13
|
+
- Pin Semgrep to `1.176.0` with `--only-binary :all:` in CI to prevent setup script execution and lock resolved versions (SonarQube: `githubactions:S8541`, `githubactions:S8544`)
|
|
14
|
+
- Wrap `normalize` and `resultElement` callbacks in anonymous functions in `.map()` calls to avoid passing unexpected iterator arguments (SonarQube: `javascript:S7727`)
|
|
15
|
+
- Wrap `search` callback in anonymous function in `.forEach()` call in benchmark (SonarQube: `javascript:S7727`)
|
|
16
|
+
- Replace `parseInt` with `Number.parseInt` in dropdown and related runtimes (SonarQube: `javascript:S7773`)
|
|
17
|
+
|
|
18
|
+
## 0.3.3 — 2026-09-11
|
|
19
|
+
|
|
20
|
+
### Refactored
|
|
21
|
+
- Extract duplicated `normalize` function from `client-search-base.js` and `client-search-dropdown.js` into new shared module `assets/client-search-shared.js` (exposes `window.ClientSearchShared.normalize` and `window.ClientSearchShared.coreFields`) — eliminates CodeFactor duplicate-code finding and ensures consistent normalization across both runtimes
|
|
22
|
+
|
|
5
23
|
## 0.3.2 — 2026-09-10
|
|
6
24
|
|
|
7
25
|
### Added
|
data/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
[](#testing-strategy)
|
|
11
11
|
[](https://semgrep.dev)
|
|
12
12
|
[](https://www.codefactor.io/repository/github/gundestrup/jekyll-client-search)
|
|
13
|
+
[](https://sonarcloud.io/project/overview?id=gundestrup_jekyll-client-search)
|
|
13
14
|
|
|
14
15
|
A Jekyll plugin that generates a JSON document index for client-side search.
|
|
15
16
|
Supports lexical search ([MiniSearch](https://lucaong.github.io/minisearch/),
|
|
@@ -58,55 +58,15 @@
|
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
"categories", "tags", "categoriesText", "tagsText",
|
|
64
|
-
"date", "date_timestamp", "embedding"
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
function normalize(entry) {
|
|
68
|
-
var id = entry.id || entry.url;
|
|
69
|
-
if (!id) {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
var categories = Array.isArray(entry.categories) ? entry.categories : [];
|
|
73
|
-
var tags = Array.isArray(entry.tags) ? entry.tags : [];
|
|
74
|
-
var normalized = {
|
|
75
|
-
id: id,
|
|
76
|
-
title: entry.title || "Untitled",
|
|
77
|
-
url: entry.url || "#",
|
|
78
|
-
excerpt: entry.excerpt || "",
|
|
79
|
-
content: entry.content || "",
|
|
80
|
-
date: entry.date || "",
|
|
81
|
-
date_timestamp: Number(entry.date_timestamp) || 0,
|
|
82
|
-
categories: categories,
|
|
83
|
-
tags: tags,
|
|
84
|
-
categoriesText: categories.join(" "),
|
|
85
|
-
tagsText: tags.join(" ")
|
|
86
|
-
};
|
|
87
|
-
if (entry.source) {
|
|
88
|
-
normalized.source = entry.source;
|
|
89
|
-
}
|
|
90
|
-
if (Array.isArray(entry.embedding) && entry.embedding.length > 0) {
|
|
91
|
-
normalized.embedding = entry.embedding;
|
|
92
|
-
}
|
|
93
|
-
Object.keys(entry).forEach(function (key) {
|
|
94
|
-
if (coreFields.indexOf(key) === -1 && key !== "source" && !(key in normalized)) {
|
|
95
|
-
var value = entry[key];
|
|
96
|
-
if (value !== null && value !== undefined && value !== "") {
|
|
97
|
-
normalized[key] = value;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
return normalized;
|
|
102
|
-
}
|
|
61
|
+
var normalize = window.ClientSearchShared.normalize;
|
|
62
|
+
var coreFields = window.ClientSearchShared.coreFields;
|
|
103
63
|
|
|
104
64
|
function buildIndex(data) {
|
|
105
65
|
if (!Array.isArray(data)) {
|
|
106
66
|
throw new TypeError("Search index must be an array");
|
|
107
67
|
}
|
|
108
68
|
|
|
109
|
-
var documents = data.map(normalize).filter(Boolean);
|
|
69
|
+
var documents = data.map(function (entry) { return normalize(entry); }).filter(Boolean);
|
|
110
70
|
documentsById = new Map(documents.map(function (entry) {
|
|
111
71
|
return [entry.id, entry];
|
|
112
72
|
}));
|
|
@@ -272,7 +232,7 @@
|
|
|
272
232
|
}
|
|
273
233
|
results.replaceChildren.apply(
|
|
274
234
|
results,
|
|
275
|
-
matches.map(resultElement).filter(Boolean)
|
|
235
|
+
matches.map(function (match) { return resultElement(match); }).filter(Boolean)
|
|
276
236
|
);
|
|
277
237
|
}
|
|
278
238
|
|
|
@@ -41,39 +41,7 @@
|
|
|
41
41
|
return key.replace(/_([a-z])/g, function (_, char) { return char.toUpperCase(); });
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
var id = entry.id || entry.url;
|
|
46
|
-
if (!id) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
var categories = Array.isArray(entry.categories) ? entry.categories : [];
|
|
50
|
-
var tags = Array.isArray(entry.tags) ? entry.tags : [];
|
|
51
|
-
var normalized = {
|
|
52
|
-
id: id,
|
|
53
|
-
title: entry.title || "Untitled",
|
|
54
|
-
url: entry.url || "#",
|
|
55
|
-
excerpt: entry.excerpt || "",
|
|
56
|
-
content: entry.content || "",
|
|
57
|
-
date: entry.date || "",
|
|
58
|
-
date_timestamp: Number(entry.date_timestamp) || 0,
|
|
59
|
-
categories: categories,
|
|
60
|
-
tags: tags,
|
|
61
|
-
categoriesText: categories.join(" "),
|
|
62
|
-
tagsText: tags.join(" ")
|
|
63
|
-
};
|
|
64
|
-
if (entry.source) {
|
|
65
|
-
normalized.source = entry.source;
|
|
66
|
-
}
|
|
67
|
-
Object.keys(entry).forEach(function (key) {
|
|
68
|
-
if (!(key in normalized) && key !== "embedding") {
|
|
69
|
-
var value = entry[key];
|
|
70
|
-
if (value !== null && value !== undefined && value !== "") {
|
|
71
|
-
normalized[key] = value;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
return normalized;
|
|
76
|
-
}
|
|
44
|
+
var normalize = window.ClientSearchShared.normalize;
|
|
77
45
|
|
|
78
46
|
function loadIndex(config) {
|
|
79
47
|
if (sharedIndex && sharedDocuments) {
|
|
@@ -99,7 +67,7 @@
|
|
|
99
67
|
if (!Array.isArray(data)) {
|
|
100
68
|
throw new TypeError("Search index must be an array");
|
|
101
69
|
}
|
|
102
|
-
var documents = data.map(normalize).filter(Boolean);
|
|
70
|
+
var documents = data.map(function (entry) { return normalize(entry); }).filter(Boolean);
|
|
103
71
|
var adapter = window.ClientSearchAdapters &&
|
|
104
72
|
(window.ClientSearchAdapters[config.engine] ||
|
|
105
73
|
window.ClientSearchAdapters.minisearch);
|
|
@@ -209,7 +177,7 @@
|
|
|
209
177
|
this.input = root.querySelector("[data-cs-dropdown-input]");
|
|
210
178
|
this.results = root.querySelector("[data-cs-dropdown-results]");
|
|
211
179
|
this.config = config;
|
|
212
|
-
this.maxItems = parseInt(this.results.dataset.maxItems, 10) || config.maxItems || 5;
|
|
180
|
+
this.maxItems = Number.parseInt(this.results.dataset.maxItems, 10) || config.maxItems || 5;
|
|
213
181
|
this.selectedIndex = -1;
|
|
214
182
|
this.currentItems = [];
|
|
215
183
|
this.debounceTimer = null;
|
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
if (container.dataset.relatedMax) {
|
|
156
|
-
options.maxItems = parseInt(container.dataset.relatedMax, 10) || 0;
|
|
156
|
+
options.maxItems = Number.parseInt(container.dataset.relatedMax, 10) || 0;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
return fetch(options.relationsUrl, { headers: { Accept: "application/json" } })
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ClientSearch shared utilities — used by the base runtime and the
|
|
6
|
+
* dropdown runtime to normalize search index entries into a uniform
|
|
7
|
+
* document format.
|
|
8
|
+
*
|
|
9
|
+
* Loaded before client-search-base.js and client-search-dropdown.js.
|
|
10
|
+
* Exposes window.ClientSearchShared.normalize(entry).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
var CORE_FIELDS = [
|
|
14
|
+
"id", "title", "url", "excerpt", "content",
|
|
15
|
+
"categories", "tags", "categoriesText", "tagsText",
|
|
16
|
+
"date", "date_timestamp", "embedding"
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function normalize(entry) {
|
|
20
|
+
var id = entry.id || entry.url;
|
|
21
|
+
if (!id) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
var categories = Array.isArray(entry.categories) ? entry.categories : [];
|
|
25
|
+
var tags = Array.isArray(entry.tags) ? entry.tags : [];
|
|
26
|
+
var normalized = {
|
|
27
|
+
id: id,
|
|
28
|
+
title: entry.title || "Untitled",
|
|
29
|
+
url: entry.url || "#",
|
|
30
|
+
excerpt: entry.excerpt || "",
|
|
31
|
+
content: entry.content || "",
|
|
32
|
+
date: entry.date || "",
|
|
33
|
+
date_timestamp: Number(entry.date_timestamp) || 0,
|
|
34
|
+
categories: categories,
|
|
35
|
+
tags: tags,
|
|
36
|
+
categoriesText: categories.join(" "),
|
|
37
|
+
tagsText: tags.join(" ")
|
|
38
|
+
};
|
|
39
|
+
if (entry.source) {
|
|
40
|
+
normalized.source = entry.source;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(entry.embedding) && entry.embedding.length > 0) {
|
|
43
|
+
normalized.embedding = entry.embedding;
|
|
44
|
+
}
|
|
45
|
+
Object.keys(entry).forEach(function (key) {
|
|
46
|
+
if (CORE_FIELDS.indexOf(key) === -1 && key !== "source" && !(key in normalized)) {
|
|
47
|
+
var value = entry[key];
|
|
48
|
+
if (value !== null && value !== undefined && value !== "") {
|
|
49
|
+
normalized[key] = value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return normalized;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
window.ClientSearchShared = { normalize: normalize, coreFields: CORE_FIELDS };
|
|
57
|
+
})();
|
|
@@ -106,6 +106,9 @@ async function embed(query) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
self.addEventListener("message", function (event) {
|
|
109
|
+
if (event.origin !== self.location.origin) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
109
112
|
var message = event.data || {};
|
|
110
113
|
activeConfig = activeConfig || message.config || {};
|
|
111
114
|
latestRequestId = message.id;
|
|
@@ -105,7 +105,7 @@ module Jekyll
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def runtime_assets
|
|
108
|
-
assets = ["assets/client-search-base.js", "assets/adapters/#{engine}.js"]
|
|
108
|
+
assets = ["assets/client-search-shared.js", "assets/client-search-base.js", "assets/adapters/#{engine}.js"]
|
|
109
109
|
assets.concat(query_embedder_assets) if engine == "semantic" && embedding_enabled?
|
|
110
110
|
assets << "assets/client-search-related.js" if related_enabled?
|
|
111
111
|
assets << "assets/client-search-dropdown.js" if dropdown_enabled?
|
|
@@ -90,24 +90,30 @@ module Jekyll
|
|
|
90
90
|
|
|
91
91
|
def build_scripts(configuration, prefix)
|
|
92
92
|
scripts = []
|
|
93
|
-
engine = configuration
|
|
94
|
-
if engine
|
|
95
|
-
attrs = ["src=\"#{CGI.escapeHTML(engine)}\""]
|
|
96
|
-
if configuration.engine_crossorigin
|
|
97
|
-
crossorigin = CGI.escapeHTML(configuration.engine_crossorigin)
|
|
98
|
-
attrs << "crossorigin=\"#{crossorigin}\""
|
|
99
|
-
end
|
|
100
|
-
if configuration.engine_sri
|
|
101
|
-
integrity = CGI.escapeHTML(configuration.engine_sri)
|
|
102
|
-
attrs << "integrity=\"#{integrity}\""
|
|
103
|
-
end
|
|
104
|
-
scripts << "<script #{attrs.join(' ')}></script>"
|
|
105
|
-
end
|
|
93
|
+
engine = engine_script(configuration)
|
|
94
|
+
scripts << engine if engine
|
|
106
95
|
scripts << "<script src=\"#{prefix}/assets/search-runtime-config.js\"></script>"
|
|
96
|
+
scripts << "<script src=\"#{prefix}/assets/client-search-shared.js\"></script>"
|
|
107
97
|
scripts << "<script src=\"#{prefix}/assets/client-search-dropdown.js\"></script>"
|
|
108
98
|
scripts << "<script src=\"#{prefix}/assets/adapters/#{configuration.engine}.js\"></script>"
|
|
109
99
|
scripts.map { |script| " #{script}" }.join("\n")
|
|
110
100
|
end
|
|
101
|
+
|
|
102
|
+
def engine_script(configuration)
|
|
103
|
+
url = configuration.engine_url
|
|
104
|
+
return nil unless url
|
|
105
|
+
|
|
106
|
+
attrs = ["src=\"#{CGI.escapeHTML(url)}\""]
|
|
107
|
+
if configuration.engine_crossorigin
|
|
108
|
+
crossorigin = CGI.escapeHTML(configuration.engine_crossorigin)
|
|
109
|
+
attrs << "crossorigin=\"#{crossorigin}\""
|
|
110
|
+
end
|
|
111
|
+
if configuration.engine_sri
|
|
112
|
+
integrity = CGI.escapeHTML(configuration.engine_sri)
|
|
113
|
+
attrs << "integrity=\"#{integrity}\""
|
|
114
|
+
end
|
|
115
|
+
"<script #{attrs.join(' ')}></script>"
|
|
116
|
+
end
|
|
111
117
|
end
|
|
112
118
|
end
|
|
113
119
|
end
|
|
@@ -69,6 +69,7 @@ module Jekyll
|
|
|
69
69
|
scripts << engine if engine
|
|
70
70
|
scripts << "<script src=\"#{prefix}/assets/search-runtime-config.js\"></script>"
|
|
71
71
|
scripts.concat(embedder_scripts(configuration, prefix))
|
|
72
|
+
scripts << "<script src=\"#{prefix}/assets/client-search-shared.js\"></script>"
|
|
72
73
|
scripts << "<script src=\"#{prefix}/assets/client-search-base.js\"></script>"
|
|
73
74
|
scripts << "<script src=\"#{prefix}/assets/adapters/#{configuration.engine}.js\"></script>"
|
|
74
75
|
scripts.map { |script| " #{script}" }.join("\n")
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-client-search
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svend Gundestrup
|
|
@@ -48,6 +48,7 @@ files:
|
|
|
48
48
|
- assets/client-search-base.js
|
|
49
49
|
- assets/client-search-dropdown.js
|
|
50
50
|
- assets/client-search-related.js
|
|
51
|
+
- assets/client-search-shared.js
|
|
51
52
|
- assets/includes/related-articles.html
|
|
52
53
|
- assets/layouts/post-with-related.html
|
|
53
54
|
- assets/query-embedders/ollama-api.js
|