jekyll-client-search 0.3.4 → 0.3.5
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 +51 -0
- data/LICENSE +654 -15
- data/README.developer.md +23 -13
- data/README.md +18 -8
- data/assets/includes/related-articles.html +4 -2
- data/lib/jekyll/client_search/dropdown_tag.rb +24 -41
- data/lib/jekyll/client_search/related_tag.rb +23 -17
- data/lib/jekyll/client_search/script_tags.rb +41 -0
- data/lib/jekyll/client_search/search_tag.rb +20 -34
- data/lib/jekyll/client_search/version.rb +1 -1
- data/lib/jekyll/client_search.rb +1 -0
- metadata +3 -2
data/README.developer.md
CHANGED
|
@@ -8,13 +8,12 @@ See [README.md](README.md) for user-facing configuration and usage docs.
|
|
|
8
8
|
## Quick start
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
rbenv install
|
|
12
|
-
rbenv local 3.4.10
|
|
11
|
+
rbenv install # reads .ruby-version
|
|
13
12
|
bundle install
|
|
14
13
|
npm ci
|
|
15
|
-
bundle exec rspec # Ruby tests
|
|
14
|
+
bundle exec rspec # Ruby tests
|
|
16
15
|
OLLAMA_INTEGRATION=1 bundle exec rspec # all tests including Ollama integration
|
|
17
|
-
npm test # JavaScript tests
|
|
16
|
+
npm test # JavaScript tests
|
|
18
17
|
```
|
|
19
18
|
|
|
20
19
|
Both test suites pass with only the committed Wikipedia fixtures and the
|
|
@@ -29,16 +28,22 @@ The project uses a focused quality stack: RuboCop (style), bundler-audit
|
|
|
29
28
|
maintenance burden of additional code-smell tools on a small focused gem.
|
|
30
29
|
|
|
31
30
|
External automated code review services:
|
|
31
|
+
|
|
32
32
|
- [CodeQL](https://codeql.github.com) — GitHub native security scanning
|
|
33
33
|
(Ruby + JavaScript), runs on every push.
|
|
34
34
|
- [Semgrep](https://semgrep.dev) — CI security scan using the Semgrep Pro
|
|
35
|
-
engine with the organization's full policy
|
|
35
|
+
engine with the organization's full policy ruleset. Runs as a CI
|
|
36
36
|
job (`semgrep ci`) and in the pre-commit hook (custom rules only). See
|
|
37
37
|
`.semgrep.yml` for local configuration and usage instructions.
|
|
38
38
|
- [CodeFactor](https://www.codefactor.io/repository/github/gundestrup/jekyll-client-search) —
|
|
39
39
|
automated code review (RuboCop, Brakeman, bundler-audit, duplication).
|
|
40
40
|
- [Dependabot](https://docs.github.com/en/code-security/dependabot) — weekly
|
|
41
41
|
GitHub Actions version updates (SHA-pinned actions updated automatically).
|
|
42
|
+
- [Codecov](https://codecov.io/gh/gundestrup/jekyll-client-search) — coverage
|
|
43
|
+
reporting. CI uploads Cobertura XML generated by `simplecov-cobertura` from
|
|
44
|
+
the Ruby 3.4 / Node 24 matrix job; status check thresholds live in
|
|
45
|
+
`codecov.yml`. Uploads authenticate via OIDC (`id-token: write`), with the
|
|
46
|
+
`CODECOV_TOKEN` secret as a fallback.
|
|
42
47
|
|
|
43
48
|
### Semgrep usage
|
|
44
49
|
|
|
@@ -85,8 +90,9 @@ comments. Dependabot updates the SHAs and version comments automatically.
|
|
|
85
90
|
|
|
86
91
|
### Git hooks
|
|
87
92
|
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
Hooks are tracked in `bin/hooks/`; `bin/install-hooks.sh` points git at
|
|
94
|
+
them via `core.hooksPath` (no copying — tracked hooks can't drift from
|
|
95
|
+
installed ones). Run once after cloning:
|
|
90
96
|
|
|
91
97
|
```bash
|
|
92
98
|
bin/install-hooks.sh
|
|
@@ -106,11 +112,11 @@ CI. Skip either with `git commit --no-verify` or `git push --no-verify`.
|
|
|
106
112
|
### CI checks
|
|
107
113
|
|
|
108
114
|
The [CI workflow](.github/workflows/ci.yml) runs on every push and pull
|
|
109
|
-
request across Ruby 3.
|
|
115
|
+
request across Ruby 3.3/3.4 and Node 22/24:
|
|
110
116
|
|
|
111
117
|
- `bundle exec rake ci` (rspec, rubocop, syntax checks, npm test, gem build)
|
|
112
118
|
- `bundle exec bundle-audit check --update` (Ruby dependency security)
|
|
113
|
-
- `npm
|
|
119
|
+
- `npm run audit` (JavaScript dependency security gate — see `scripts/npm-audit.mjs`)
|
|
114
120
|
- `npm outdated` (non-blocking — warns about outdated npm packages)
|
|
115
121
|
- `semgrep ci` (security scan with custom rules from `.semgrep.yml`)
|
|
116
122
|
|
|
@@ -163,7 +169,7 @@ test files are `spec/related_analyzer_spec.rb`, `spec/related_tag_spec.rb`,
|
|
|
163
169
|
## What is committed
|
|
164
170
|
|
|
165
171
|
| Artifact | Committed? | Why |
|
|
166
|
-
|
|
172
|
+
| --- | --- | --- |
|
|
167
173
|
| Wikipedia fixture posts (40) | Yes | CC BY-SA 3.0 allows redistribution with attribution |
|
|
168
174
|
| arXiv fixture posts (40) | **No** | Mixed/restrictive licenses — see below |
|
|
169
175
|
| Baseline search-index JSON | Yes | Generated test artifact (indexed data, not raw text) |
|
|
@@ -193,6 +199,7 @@ ruby spec/fixtures/download_arxiv.rb
|
|
|
193
199
|
```
|
|
194
200
|
|
|
195
201
|
This script:
|
|
202
|
+
|
|
196
203
|
- Fetches 40 recent arXiv papers from 5 CS/AI subfields
|
|
197
204
|
- Downloads each PDF and extracts text with `pdftotext`
|
|
198
205
|
- Records the actual per-paper license in each post's frontmatter
|
|
@@ -244,6 +251,7 @@ To run the full integration tests that call a real Ollama server:
|
|
|
244
251
|
```
|
|
245
252
|
|
|
246
253
|
These tests:
|
|
254
|
+
|
|
247
255
|
- Build the full 80-post index with real Ollama embeddings
|
|
248
256
|
- Verify embedding dimensions and validity
|
|
249
257
|
- Test semantic search quality with concept queries
|
|
@@ -272,7 +280,7 @@ ruby spec/fixtures/generate_semantic_gold.rb
|
|
|
272
280
|
## Test categories
|
|
273
281
|
|
|
274
282
|
| Tag | Description | Requires arXiv? | Requires Ollama? |
|
|
275
|
-
|
|
283
|
+
| --- | --- | --- | --- |
|
|
276
284
|
| `:unit` | Ruby unit tests | No | No |
|
|
277
285
|
| `:system` | Ruby system tests (build fixture site) | Partially | No |
|
|
278
286
|
| `:integration` | Ruby integration tests | No | No |
|
|
@@ -298,7 +306,9 @@ always return an asset).
|
|
|
298
306
|
|
|
299
307
|
The coverage report is generated at `coverage/index.html` after each RSpec
|
|
300
308
|
run. Raw data is in `coverage/.resultset.json` (SimpleCov 1.0+ format with
|
|
301
|
-
`lines` and `branches` keys per file).
|
|
309
|
+
`lines` and `branches` keys per file). In CI (`ENV["CI"]`), SimpleCov emits
|
|
310
|
+
Cobertura XML at `coverage/coverage.xml` instead, which is uploaded to
|
|
311
|
+
Codecov by the `codecov/codecov-action` step in `ci.yml`.
|
|
302
312
|
|
|
303
313
|
To check remaining uncovered lines and branches after a run:
|
|
304
314
|
|
|
@@ -492,7 +502,7 @@ The [`release.yml`](.github/workflows/release.yml) workflow, triggered by
|
|
|
492
502
|
pushing a `v*` tag:
|
|
493
503
|
|
|
494
504
|
1. Checks out the repository at the release tag (`persist-credentials: false`).
|
|
495
|
-
2. Sets up Ruby
|
|
505
|
+
2. Sets up Ruby from `.ruby-version`.
|
|
496
506
|
3. Verifies the tag name matches `v<gem version>` (fails on mismatch).
|
|
497
507
|
4. Verifies `CHANGELOG.md` has a `## X.Y.Z` entry for the version
|
|
498
508
|
(`rake version:check_changelog` — fails if missing).
|
data/README.md
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# jekyll-client-search
|
|
2
2
|
|
|
3
|
+
[](https://codecov.io/gh/gundestrup/jekyll-client-search)
|
|
4
|
+
|
|
5
|
+
|
|
3
6
|

|
|
4
7
|
|
|
5
8
|
[](https://github.com/gundestrup/jekyll-client-search/actions/workflows/ci.yml)
|
|
6
|
-
[](https://rubygems.org/gems/jekyll-client-search)
|
|
10
|
+
[](https://www.ruby-lang.org/)
|
|
7
11
|
[](https://jekyllrb.com/)
|
|
8
12
|
[](LICENSE)
|
|
9
|
-
[](https://deepwiki.com/gundestrup/jekyll-client-search)
|
|
10
14
|
[](#testing-strategy)
|
|
11
15
|
[](https://semgrep.dev)
|
|
12
16
|
[](https://www.codefactor.io/repository/github/gundestrup/jekyll-client-search)
|
|
13
17
|
[](https://sonarcloud.io/project/overview?id=gundestrup_jekyll-client-search)
|
|
18
|
+
[](https://codecov.io/gh/gundestrup/jekyll-client-search)
|
|
14
19
|
|
|
15
20
|
A Jekyll plugin that generates a JSON document index for client-side search.
|
|
16
21
|
Supports lexical search ([MiniSearch](https://lucaong.github.io/minisearch/),
|
|
@@ -603,14 +608,19 @@ Jekyll build time using a local [Ollama](https://ollama.ai/) server and the
|
|
|
603
608
|
### Setup
|
|
604
609
|
|
|
605
610
|
1. Install [Ollama](https://ollama.ai/) and pull an embedding model:
|
|
611
|
+
|
|
606
612
|
```bash
|
|
607
613
|
ollama pull embeddinggemma:300m
|
|
608
614
|
```
|
|
615
|
+
|
|
609
616
|
2. Add `ollama-ruby` to your site's Gemfile (optional dependency):
|
|
617
|
+
|
|
610
618
|
```ruby
|
|
611
619
|
gem "ollama-ruby", "~> 1.23"
|
|
612
620
|
```
|
|
621
|
+
|
|
613
622
|
3. Enable embeddings in `_config.yml`:
|
|
623
|
+
|
|
614
624
|
```yaml
|
|
615
625
|
client_search:
|
|
616
626
|
engine: semantic
|
|
@@ -1012,18 +1022,18 @@ Recommended policy:
|
|
|
1012
1022
|
|
|
1013
1023
|
## License
|
|
1014
1024
|
|
|
1025
|
+
Copyright (C) 2026 Svend Gundestrup.
|
|
1026
|
+
|
|
1015
1027
|
This project is licensed under the GNU Affero General Public License v3.0 or
|
|
1016
1028
|
later (`AGPL-3.0-or-later`). See [LICENSE](LICENSE).
|
|
1017
1029
|
|
|
1018
1030
|
## Development
|
|
1019
1031
|
|
|
1020
|
-
Development uses Ruby
|
|
1021
|
-
and newer.
|
|
1022
|
-
Ruby 3.2, 3.3, and 3.4.
|
|
1032
|
+
Development uses the Ruby version pinned in `.ruby-version` (via rbenv),
|
|
1033
|
+
while the gem supports Ruby 3.3 and newer. CI tests Ruby 3.3 and 3.4.
|
|
1023
1034
|
|
|
1024
1035
|
```bash
|
|
1025
|
-
rbenv install
|
|
1026
|
-
rbenv local 3.4.10
|
|
1036
|
+
rbenv install # reads .ruby-version
|
|
1027
1037
|
bundle install
|
|
1028
1038
|
npm ci
|
|
1029
1039
|
bundle exec rake ci
|
|
@@ -1153,7 +1163,7 @@ Additional automated code quality and security services:
|
|
|
1153
1163
|
- [CodeQL](https://codeql.github.com) — GitHub native code scanning for
|
|
1154
1164
|
security vulnerabilities (Ruby + JavaScript).
|
|
1155
1165
|
- [Semgrep](https://semgrep.dev) — CI security scan using the Semgrep Pro
|
|
1156
|
-
engine with the organization's full policy
|
|
1166
|
+
engine with the organization's full policy ruleset. Custom ReDoS
|
|
1157
1167
|
detection rules are defined in `.semgrep.yml`. Findings are triaged on the
|
|
1158
1168
|
[Semgrep AppSec Platform](https://semgrep.dev) dashboard. False positives
|
|
1159
1169
|
and acceptable risks are annotated inline with `# nosemgrep: rule.id`.
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
# scripts: true (default) to load runtime scripts, false to skip
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
{%
|
|
19
|
-
|
|
18
|
+
{% comment %}Bracket access: bare include.sort falls back to Hash#sort on
|
|
19
|
+
non-empty param hashes, so the default would silently stop applying.{% endcomment %}
|
|
20
|
+
{% assign related_sort = include["sort"] | default: "relevance" %}
|
|
21
|
+
{% assign include_scripts = include["scripts"] | default: true %}
|
|
20
22
|
{% if include_scripts == false or include_scripts == "false" %}
|
|
21
23
|
{% assign include_scripts = false %}
|
|
22
24
|
{% endif %}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "cgi"
|
|
4
|
-
|
|
5
3
|
module Jekyll
|
|
6
4
|
module ClientSearch
|
|
7
5
|
# Liquid tag that renders a compact live-search dropdown suitable for
|
|
@@ -15,27 +13,22 @@ module Jekyll
|
|
|
15
13
|
#
|
|
16
14
|
# When client_search is disabled the tag renders nothing.
|
|
17
15
|
class DropdownTag < Liquid::Tag
|
|
16
|
+
include ScriptTags
|
|
17
|
+
|
|
18
18
|
SYNTAX = /\A(max:(\d+))?\s*(scripts_only|no_scripts)?\z/
|
|
19
19
|
|
|
20
20
|
def initialize(tag_name, markup, tokens)
|
|
21
21
|
super
|
|
22
|
+
# No validation here: `{% comment %}` still parses nested tags, so
|
|
23
|
+
# `initialize` runs on commented-out tags. Validate in `render` —
|
|
24
|
+
# a bad tag inside a comment must not break the build.
|
|
22
25
|
@markup = markup.to_s.strip
|
|
23
|
-
unless (match = @markup.match(SYNTAX))
|
|
24
|
-
raise Liquid::SyntaxError,
|
|
25
|
-
"search_dropdown: invalid syntax. Use {% search_dropdown %}, " \
|
|
26
|
-
"{% search_dropdown max:10 %}, {% search_dropdown scripts_only %}, " \
|
|
27
|
-
"or {% search_dropdown no_scripts %}"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
@max_items = match[2].to_i if match[2]
|
|
31
|
-
raise Liquid::SyntaxError, "search_dropdown: max must be greater than zero" if @max_items && @max_items < 1
|
|
32
|
-
|
|
33
|
-
@mode = match[3] || "full"
|
|
34
26
|
@input_id = "cs-dropdown-input-#{object_id}"
|
|
35
27
|
@results_id = "cs-dropdown-results-#{object_id}"
|
|
36
28
|
end
|
|
37
29
|
|
|
38
30
|
def render(context)
|
|
31
|
+
validate_markup!
|
|
39
32
|
site = context.registers[:site]
|
|
40
33
|
return "" unless enabled?(site)
|
|
41
34
|
|
|
@@ -47,6 +40,23 @@ module Jekyll
|
|
|
47
40
|
|
|
48
41
|
private
|
|
49
42
|
|
|
43
|
+
def validate_markup!
|
|
44
|
+
return if @validated
|
|
45
|
+
|
|
46
|
+
@validated = true
|
|
47
|
+
unless (match = @markup.match(SYNTAX))
|
|
48
|
+
raise Liquid::SyntaxError,
|
|
49
|
+
"search_dropdown: invalid syntax. Use {% search_dropdown %}, " \
|
|
50
|
+
"{% search_dropdown max:10 %}, {% search_dropdown scripts_only %}, " \
|
|
51
|
+
"or {% search_dropdown no_scripts %}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@max_items = match[2].to_i if match[2]
|
|
55
|
+
raise Liquid::SyntaxError, "search_dropdown: max must be greater than zero" if @max_items && @max_items < 1
|
|
56
|
+
|
|
57
|
+
@mode = match[3] || "full"
|
|
58
|
+
end
|
|
59
|
+
|
|
50
60
|
def enabled?(site)
|
|
51
61
|
return false if site.nil?
|
|
52
62
|
|
|
@@ -62,7 +72,7 @@ module Jekyll
|
|
|
62
72
|
form_html = build_form(max)
|
|
63
73
|
return form_html if @mode == "no_scripts"
|
|
64
74
|
|
|
65
|
-
scripts =
|
|
75
|
+
scripts = script_tags(configuration, prefix, "client-search-dropdown.js")
|
|
66
76
|
return scripts if @mode == "scripts_only"
|
|
67
77
|
|
|
68
78
|
"#{form_html}\n#{scripts}"
|
|
@@ -87,33 +97,6 @@ module Jekyll
|
|
|
87
97
|
</div>
|
|
88
98
|
HTML
|
|
89
99
|
end
|
|
90
|
-
|
|
91
|
-
def build_scripts(configuration, prefix)
|
|
92
|
-
scripts = []
|
|
93
|
-
engine = engine_script(configuration)
|
|
94
|
-
scripts << engine if engine
|
|
95
|
-
scripts << "<script src=\"#{prefix}/assets/search-runtime-config.js\"></script>"
|
|
96
|
-
scripts << "<script src=\"#{prefix}/assets/client-search-shared.js\"></script>"
|
|
97
|
-
scripts << "<script src=\"#{prefix}/assets/client-search-dropdown.js\"></script>"
|
|
98
|
-
scripts << "<script src=\"#{prefix}/assets/adapters/#{configuration.engine}.js\"></script>"
|
|
99
|
-
scripts.map { |script| " #{script}" }.join("\n")
|
|
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
|
|
117
100
|
end
|
|
118
101
|
end
|
|
119
102
|
end
|
|
@@ -18,11 +18,34 @@ module Jekyll
|
|
|
18
18
|
|
|
19
19
|
def initialize(tag_name, markup, tokens)
|
|
20
20
|
super
|
|
21
|
+
# No validation here: `{% comment %}` still parses nested tags, so
|
|
22
|
+
# `initialize` runs on commented-out tags. Validate in `render` —
|
|
23
|
+
# a bad tag inside a comment must not break the build.
|
|
21
24
|
@markup = markup.to_s.strip
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render(context)
|
|
22
28
|
parse_tokens(@markup)
|
|
29
|
+
site = context.registers[:site]
|
|
30
|
+
return "" if site.nil?
|
|
31
|
+
|
|
32
|
+
config = site.config.fetch("client_search", {})
|
|
33
|
+
return "" if config == false
|
|
34
|
+
return "" unless related_enabled?(config)
|
|
35
|
+
|
|
36
|
+
asset_prefix = asset_prefix(site)
|
|
37
|
+
sort_attr = @sort ? " data-related-sort=\"#{@sort}\"" : ""
|
|
38
|
+
max_attr = resolve_max_attr(config)
|
|
39
|
+
scripts = build_scripts(asset_prefix)
|
|
40
|
+
build_html(sort_attr, max_attr, scripts)
|
|
23
41
|
end
|
|
24
42
|
|
|
43
|
+
private
|
|
44
|
+
|
|
25
45
|
def parse_tokens(markup)
|
|
46
|
+
return if @parsed
|
|
47
|
+
|
|
48
|
+
@parsed = true
|
|
26
49
|
@sort = nil
|
|
27
50
|
@max_items = nil
|
|
28
51
|
@include_scripts = true
|
|
@@ -49,23 +72,6 @@ module Jekyll
|
|
|
49
72
|
"or {% related_articles no_scripts %}"
|
|
50
73
|
end
|
|
51
74
|
|
|
52
|
-
def render(context)
|
|
53
|
-
site = context.registers[:site]
|
|
54
|
-
return "" if site.nil?
|
|
55
|
-
|
|
56
|
-
config = site.config.fetch("client_search", {})
|
|
57
|
-
return "" if config == false
|
|
58
|
-
return "" unless related_enabled?(config)
|
|
59
|
-
|
|
60
|
-
asset_prefix = asset_prefix(site)
|
|
61
|
-
sort_attr = @sort ? " data-related-sort=\"#{@sort}\"" : ""
|
|
62
|
-
max_attr = resolve_max_attr(config)
|
|
63
|
-
scripts = build_scripts(asset_prefix)
|
|
64
|
-
build_html(sort_attr, max_attr, scripts)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
private
|
|
68
|
-
|
|
69
75
|
def related_enabled?(config)
|
|
70
76
|
related = config.fetch("related", {})
|
|
71
77
|
related["enabled"] == true
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
module ClientSearch
|
|
7
|
+
# Shared <script> tag assembly for the client-search Liquid tags so each
|
|
8
|
+
# tag only declares its own asset file.
|
|
9
|
+
module ScriptTags
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def script_tags(configuration, prefix, tag_asset, extra: [])
|
|
13
|
+
scripts = []
|
|
14
|
+
engine = engine_script(configuration)
|
|
15
|
+
scripts << engine if engine
|
|
16
|
+
scripts << "<script src=\"#{prefix}/assets/search-runtime-config.js\"></script>"
|
|
17
|
+
scripts.concat(extra)
|
|
18
|
+
scripts << "<script src=\"#{prefix}/assets/client-search-shared.js\"></script>"
|
|
19
|
+
scripts << "<script src=\"#{prefix}/assets/#{tag_asset}\"></script>"
|
|
20
|
+
scripts << "<script src=\"#{prefix}/assets/adapters/#{configuration.engine}.js\"></script>"
|
|
21
|
+
scripts.map { |script| " #{script}" }.join("\n")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def engine_script(configuration)
|
|
25
|
+
url = configuration.engine_url
|
|
26
|
+
return nil unless url
|
|
27
|
+
|
|
28
|
+
attrs = ["src=\"#{CGI.escapeHTML(url)}\""]
|
|
29
|
+
if configuration.engine_crossorigin
|
|
30
|
+
crossorigin = CGI.escapeHTML(configuration.engine_crossorigin)
|
|
31
|
+
attrs << "crossorigin=\"#{crossorigin}\""
|
|
32
|
+
end
|
|
33
|
+
if configuration.engine_sri
|
|
34
|
+
integrity = CGI.escapeHTML(configuration.engine_sri)
|
|
35
|
+
attrs << "integrity=\"#{integrity}\""
|
|
36
|
+
end
|
|
37
|
+
"<script #{attrs.join(' ')}></script>"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "cgi"
|
|
4
|
-
|
|
5
3
|
module Jekyll
|
|
6
4
|
module ClientSearch
|
|
7
5
|
# Liquid tag that renders the search form, status/results containers,
|
|
@@ -14,21 +12,20 @@ module Jekyll
|
|
|
14
12
|
#
|
|
15
13
|
# When client_search is disabled the tag renders nothing.
|
|
16
14
|
class SearchTag < Liquid::Tag
|
|
15
|
+
include ScriptTags
|
|
16
|
+
|
|
17
17
|
SYNTAX = /\A(scripts_only|no_scripts)?\z/
|
|
18
18
|
|
|
19
19
|
def initialize(tag_name, markup, tokens)
|
|
20
20
|
super
|
|
21
|
+
# No validation here: `{% comment %}` still parses nested tags, so
|
|
22
|
+
# `initialize` runs on commented-out tags. Validate in `render` —
|
|
23
|
+
# a bad tag inside a comment must not break the build.
|
|
21
24
|
@markup = markup.to_s.strip
|
|
22
|
-
unless (match = @markup.match(SYNTAX))
|
|
23
|
-
raise Liquid::SyntaxError,
|
|
24
|
-
"search_form: invalid syntax. Use {% search_form %}, " \
|
|
25
|
-
"{% search_form scripts_only %}, or {% search_form no_scripts %}"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
@mode = match[1] || "full"
|
|
29
25
|
end
|
|
30
26
|
|
|
31
27
|
def render(context)
|
|
28
|
+
validate_markup!
|
|
32
29
|
site = context.registers[:site]
|
|
33
30
|
return "" if site.nil?
|
|
34
31
|
|
|
@@ -51,6 +48,18 @@ module Jekyll
|
|
|
51
48
|
|
|
52
49
|
private
|
|
53
50
|
|
|
51
|
+
def validate_markup!
|
|
52
|
+
return if @mode
|
|
53
|
+
|
|
54
|
+
unless (match = @markup.match(SYNTAX))
|
|
55
|
+
raise Liquid::SyntaxError,
|
|
56
|
+
"search_form: invalid syntax. Use {% search_form %}, " \
|
|
57
|
+
"{% search_form scripts_only %}, or {% search_form no_scripts %}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@mode = match[1] || "full"
|
|
61
|
+
end
|
|
62
|
+
|
|
54
63
|
def build_form
|
|
55
64
|
<<~HTML
|
|
56
65
|
<form id="search-form" role="search">
|
|
@@ -64,31 +73,8 @@ module Jekyll
|
|
|
64
73
|
end
|
|
65
74
|
|
|
66
75
|
def build_scripts(configuration, prefix)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
scripts << engine if engine
|
|
70
|
-
scripts << "<script src=\"#{prefix}/assets/search-runtime-config.js\"></script>"
|
|
71
|
-
scripts.concat(embedder_scripts(configuration, prefix))
|
|
72
|
-
scripts << "<script src=\"#{prefix}/assets/client-search-shared.js\"></script>"
|
|
73
|
-
scripts << "<script src=\"#{prefix}/assets/client-search-base.js\"></script>"
|
|
74
|
-
scripts << "<script src=\"#{prefix}/assets/adapters/#{configuration.engine}.js\"></script>"
|
|
75
|
-
scripts.map { |script| " #{script}" }.join("\n")
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def engine_script(configuration)
|
|
79
|
-
url = configuration.engine_url
|
|
80
|
-
return nil unless url
|
|
81
|
-
|
|
82
|
-
attrs = ["src=\"#{CGI.escapeHTML(url)}\""]
|
|
83
|
-
if configuration.engine_crossorigin
|
|
84
|
-
crossorigin = CGI.escapeHTML(configuration.engine_crossorigin)
|
|
85
|
-
attrs << "crossorigin=\"#{crossorigin}\""
|
|
86
|
-
end
|
|
87
|
-
if configuration.engine_sri
|
|
88
|
-
integrity = CGI.escapeHTML(configuration.engine_sri)
|
|
89
|
-
attrs << "integrity=\"#{integrity}\""
|
|
90
|
-
end
|
|
91
|
-
"<script #{attrs.join(' ')}></script>"
|
|
76
|
+
script_tags(configuration, prefix, "client-search-base.js",
|
|
77
|
+
extra: embedder_scripts(configuration, prefix))
|
|
92
78
|
end
|
|
93
79
|
|
|
94
80
|
def embedder_scripts(configuration, prefix)
|
data/lib/jekyll/client_search.rb
CHANGED
|
@@ -23,5 +23,6 @@ require_relative "client_search/index_cache"
|
|
|
23
23
|
require_relative "client_search/ollama_embedding_adapter"
|
|
24
24
|
require_relative "client_search/generator"
|
|
25
25
|
require_relative "client_search/related_tag"
|
|
26
|
+
require_relative "client_search/script_tags"
|
|
26
27
|
require_relative "client_search/dropdown_tag"
|
|
27
28
|
require_relative "client_search/search_tag"
|
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.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svend Gundestrup
|
|
@@ -75,6 +75,7 @@ files:
|
|
|
75
75
|
- lib/jekyll/client_search/related_page.rb
|
|
76
76
|
- lib/jekyll/client_search/related_tag.rb
|
|
77
77
|
- lib/jekyll/client_search/runtime_config_page.rb
|
|
78
|
+
- lib/jekyll/client_search/script_tags.rb
|
|
78
79
|
- lib/jekyll/client_search/search_index_page.rb
|
|
79
80
|
- lib/jekyll/client_search/search_tag.rb
|
|
80
81
|
- lib/jekyll/client_search/tasks.rb
|
|
@@ -95,7 +96,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
95
96
|
requirements:
|
|
96
97
|
- - ">="
|
|
97
98
|
- !ruby/object:Gem::Version
|
|
98
|
-
version: 3.
|
|
99
|
+
version: 3.3.0
|
|
99
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
101
|
requirements:
|
|
101
102
|
- - ">="
|