jekyll-client-search 0.3.3 → 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.
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 3.4.10 # if not already installed
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 (248 examples; 7 Ollama pending)
14
+ bundle exec rspec # Ruby tests
16
15
  OLLAMA_INTEGRATION=1 bundle exec rspec # all tests including Ollama integration
17
- npm test # JavaScript tests (134 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 (2,900+ rules). Runs as a CI
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
- Git hooks are not committed to the repository. Install them locally after
89
- cloning:
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.2/3.3/3.4 and Node 22/24:
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 audit --audit-level=high` (JavaScript dependency security)
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 3.4.10.
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,15 +1,21 @@
1
1
  # jekyll-client-search
2
2
 
3
+ [![codecov](https://codecov.io/gh/gundestrup/jekyll-client-search/branch/main/graph/badge.svg)](https://codecov.io/gh/gundestrup/jekyll-client-search)
4
+
5
+
3
6
  ![jekyll-client-search logo](docs/assets/icon-256.png)
4
7
 
5
8
  [![CI](https://github.com/gundestrup/jekyll-client-search/actions/workflows/ci.yml/badge.svg)](https://github.com/gundestrup/jekyll-client-search/actions/workflows/ci.yml)
6
- [![Ruby](https://img.shields.io/badge/ruby-%E2%89%A5%203.2-red.svg)](https://www.ruby-lang.org/)
9
+ [![Gem Version](https://img.shields.io/gem/v/jekyll-client-search)](https://rubygems.org/gems/jekyll-client-search)
10
+ [![Ruby](https://img.shields.io/badge/ruby-%E2%89%A5%203.3-red.svg)](https://www.ruby-lang.org/)
7
11
  [![Jekyll](https://img.shields.io/badge/jekyll-4.x-blue.svg)](https://jekyllrb.com/)
8
12
  [![License: AGPL v3](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg)](LICENSE)
9
- [![DeepWiki](https://img.shields.io/badge/DeepWiki-docs-7B68EE.svg)](https://deepwiki.com/gundestrup/jekyll-client-search)
13
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/gundestrup/jekyll-client-search)
10
14
  [![Coverage](https://img.shields.io/badge/coverage-100%25%20branches-brightgreen.svg)](#testing-strategy)
11
15
  [![Semgrep](https://img.shields.io/badge/semgrep-scanning-brightgreen.svg)](https://semgrep.dev)
12
16
  [![CodeFactor](https://www.codefactor.io/repository/github/gundestrup/jekyll-client-search/badge)](https://www.codefactor.io/repository/github/gundestrup/jekyll-client-search)
17
+ [![SonarQube](https://sonarcloud.io/api/project_badges/measure?project=gundestrup_jekyll-client-search&metric=alert_status)](https://sonarcloud.io/project/overview?id=gundestrup_jekyll-client-search)
18
+ [![codecov](https://codecov.io/gh/gundestrup/jekyll-client-search/branch/main/graph/badge.svg)](https://codecov.io/gh/gundestrup/jekyll-client-search)
13
19
 
14
20
  A Jekyll plugin that generates a JSON document index for client-side search.
15
21
  Supports lexical search ([MiniSearch](https://lucaong.github.io/minisearch/),
@@ -602,14 +608,19 @@ Jekyll build time using a local [Ollama](https://ollama.ai/) server and the
602
608
  ### Setup
603
609
 
604
610
  1. Install [Ollama](https://ollama.ai/) and pull an embedding model:
611
+
605
612
  ```bash
606
613
  ollama pull embeddinggemma:300m
607
614
  ```
615
+
608
616
  2. Add `ollama-ruby` to your site's Gemfile (optional dependency):
617
+
609
618
  ```ruby
610
619
  gem "ollama-ruby", "~> 1.23"
611
620
  ```
621
+
612
622
  3. Enable embeddings in `_config.yml`:
623
+
613
624
  ```yaml
614
625
  client_search:
615
626
  engine: semantic
@@ -1011,18 +1022,18 @@ Recommended policy:
1011
1022
 
1012
1023
  ## License
1013
1024
 
1025
+ Copyright (C) 2026 Svend Gundestrup.
1026
+
1014
1027
  This project is licensed under the GNU Affero General Public License v3.0 or
1015
1028
  later (`AGPL-3.0-or-later`). See [LICENSE](LICENSE).
1016
1029
 
1017
1030
  ## Development
1018
1031
 
1019
- Development uses Ruby 3.4.10 through rbenv, while the gem supports Ruby 3.2
1020
- and newer. The development version is stored in `.ruby-version`, and CI tests
1021
- 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.
1022
1034
 
1023
1035
  ```bash
1024
- rbenv install 3.4.10 # if not already installed
1025
- rbenv local 3.4.10
1036
+ rbenv install # reads .ruby-version
1026
1037
  bundle install
1027
1038
  npm ci
1028
1039
  bundle exec rake ci
@@ -1152,7 +1163,7 @@ Additional automated code quality and security services:
1152
1163
  - [CodeQL](https://codeql.github.com) — GitHub native code scanning for
1153
1164
  security vulnerabilities (Ruby + JavaScript).
1154
1165
  - [Semgrep](https://semgrep.dev) — CI security scan using the Semgrep Pro
1155
- engine with the organization's full policy (2,900+ rules). Custom ReDoS
1166
+ engine with the organization's full policy ruleset. Custom ReDoS
1156
1167
  detection rules are defined in `.semgrep.yml`. Findings are triaged on the
1157
1168
  [Semgrep AppSec Platform](https://semgrep.dev) dashboard. False positives
1158
1169
  and acceptable risks are annotated inline with `# nosemgrep: rule.id`.
@@ -66,7 +66,7 @@
66
66
  throw new TypeError("Search index must be an array");
67
67
  }
68
68
 
69
- var documents = data.map(normalize).filter(Boolean);
69
+ var documents = data.map(function (entry) { return normalize(entry); }).filter(Boolean);
70
70
  documentsById = new Map(documents.map(function (entry) {
71
71
  return [entry.id, entry];
72
72
  }));
@@ -232,7 +232,7 @@
232
232
  }
233
233
  results.replaceChildren.apply(
234
234
  results,
235
- matches.map(resultElement).filter(Boolean)
235
+ matches.map(function (match) { return resultElement(match); }).filter(Boolean)
236
236
  );
237
237
  }
238
238
 
@@ -67,7 +67,7 @@
67
67
  if (!Array.isArray(data)) {
68
68
  throw new TypeError("Search index must be an array");
69
69
  }
70
- var documents = data.map(normalize).filter(Boolean);
70
+ var documents = data.map(function (entry) { return normalize(entry); }).filter(Boolean);
71
71
  var adapter = window.ClientSearchAdapters &&
72
72
  (window.ClientSearchAdapters[config.engine] ||
73
73
  window.ClientSearchAdapters.minisearch);
@@ -177,7 +177,7 @@
177
177
  this.input = root.querySelector("[data-cs-dropdown-input]");
178
178
  this.results = root.querySelector("[data-cs-dropdown-results]");
179
179
  this.config = config;
180
- this.maxItems = parseInt(this.results.dataset.maxItems, 10) || config.maxItems || 5;
180
+ this.maxItems = Number.parseInt(this.results.dataset.maxItems, 10) || config.maxItems || 5;
181
181
  this.selectedIndex = -1;
182
182
  this.currentItems = [];
183
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" } })
@@ -15,8 +15,10 @@
15
15
  # scripts: true (default) to load runtime scripts, false to skip
16
16
  ---
17
17
 
18
- {% assign related_sort = include.sort | default: "relevance" %}
19
- {% assign include_scripts = include.scripts | default: true %}
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 %}
@@ -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;
@@ -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 = build_scripts(configuration, prefix)
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
- scripts = []
68
- engine = engine_script(configuration)
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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module ClientSearch
5
- VERSION = "0.3.3"
5
+ VERSION = "0.3.5"
6
6
  end
7
7
  end
@@ -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.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.2.0
99
+ version: 3.3.0
99
100
  required_rubygems_version: !ruby/object:Gem::Requirement
100
101
  requirements:
101
102
  - - ">="