jekyll-documents 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d806534ca1d7d2b56106885f003e99845646369f842d03a01ac330a5f8dbaacc
4
- data.tar.gz: 50ff80ef20340823fd852265f87f90d6eb630aa56e3795cfc2ca3a58b94fb612
3
+ metadata.gz: 3865d4ab22aa0cf85e53ad31d222e4e4d025f7f53fb1271a949d326ee8ad60a3
4
+ data.tar.gz: 483721501a072d99ade0088e958b52e02583b4d13de7ee26ecd74bc7141b9c87
5
5
  SHA512:
6
- metadata.gz: cf282187748db50d46e256dcee6f2f9b8b7e34f5e265acf35b4b1d4a5bda55950b66a668000f58aacad9195241c0b8e30e84fd06ad493be2cbc4f238cb3b28ed
7
- data.tar.gz: 95bd40229f6c2cd987e35bf6fab49d89f09a6070bbacf2bb5cedf412866bc368f458e367804a19e4f2fa1479cd2cfdbfab13acfce0ccc9af69aee9fa9be2b294
6
+ metadata.gz: c923ebb2b6e801ab1449b76ed999e8408d234ddb03c03c5b6bf21086c54a49a6fbc7d74b258049248ae53c159c81814d7f23ee44e1f590d9c89f49814bf56630
7
+ data.tar.gz: 639afd3c1abce1196a31b7fb7e34acc197d681f5dc863c53b480f7d127b104db0e27c9adb02c88889dcc62977f4d86162bfade3a077ea269f811e0f24a6da3da
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+
4
+ ## [0.3.0] - 2026-07-23
5
+
6
+ ### Added
7
+ - **Baked icon data**: Generator now bakes `icon_url` and `icon_set` into each document's data at build time, eliminating the need for Liquid context in filters
8
+ - **`FileTypeIcons.icon_for` class method**: Allows icon URL resolution without a Liquid context (used by generator)
9
+ - **Search JavaScript wired up**: `_includes/documents_search.html` now loads Lunr.js and `documents-search.js` for out-of-the-box client-side search
10
+ - **jekyll-sitemap integration**: `TextStaticFile` exposes `sitemap: false` data to exclude generated JSON index from sitemap
11
+ - **Unit tests for `FileTypeIcons.icon_for`**: 5 new tests covering all icon sets, fallbacks, and nil handling
12
+ - **Generator tests for icon data**: Tests verifying `icon_url` and `icon_set` are correctly baked into document data
13
+ - **System test for full build pipeline**: End-to-end test covering generator → JSON index → tag rendering
14
+
15
+ ### Changed
16
+ - **Minimum Ruby version**: Bumped from `>= 2.7` to `>= 3.1` (Ruby 2.7 EOL March 2023, 3.0 EOL April 2024)
17
+ - **RuboCop `TargetRubyVersion`**: Updated from `2.7` to `3.1` to match gemspec
18
+ - **Templates use baked icon data**: `latest_documents.html`, `documents_list.html`, `document.html` now use `{{ doc.icon_url }}` instead of the broken `file_type_icon_tag` filter
19
+ - **`category_list.html` icon set**: Now reads `icon_set` from first document's baked data instead of non-functional `site.documents.icon_set`
20
+ - **`latest_documents.html` count fallback**: Simplified to `| default: 5` since `site.config` is inaccessible in Liquid
21
+ - **Reek configuration**: Updated `.reek.yml` with targeted exclusions for design-inherent warnings
22
+ - **Code quality**: Renamed uncommunicative variables (`s` → `slug`/`result`, `d` → `doc`, `m` → `match`, `y/m/d` → `year/month/day`) across all source files
23
+ - **Reduced duplicate method calls**: Extracted `data = doc.data` locals in generator, JSON index generator, and latest documents tag
24
+ - **Simplified `get_icon_set`**: Reduced from 6 statements to 3 using safe navigation with `dig`
25
+ - **Test suite**: 167 examples (up from 79), 100% line coverage (212/212 lines)
26
+ - **RuboCop**: 0 offenses (24 files inspected)
27
+ - **Reek**: 0 warnings (7 files inspected)
28
+
29
+ ### Fixed
30
+ - **Icon set config ignored in templates** (High): `file_type_icon_tag` filter never received Liquid context, causing `icon_set` config to be silently ignored — always fell back to `"color"`. Fixed by baking icon URLs into document data at generation time
31
+ - **`rel_path` slicing** (Low): Replaced fragile `path[(site.source.length + 1)..]` with `String#delete_prefix` for robustness
32
+ - **`Date.parse` in `parse_filename`** (Low): Replaced with `Date.new(year, month, day)` using integer-converted regex captures for clarity
33
+ - **`category_list.html` folder icon** (Low): `site.documents.icon_set` always resolved to `nil` since `site.documents` is an array — now reads from baked document data
34
+ - **Search include missing JavaScript** (Medium): `documents_search.html` had input/results elements but no script tags — now loads Lunr.js and search JS
35
+ - **No active jekyll-sitemap integration** (Low): JSON index file now flagged with `sitemap: false` to exclude it from sitemap
36
+
3
37
  ## [0.2.0] - 2026-03-12
4
38
 
5
39
  ### Added
@@ -1,8 +1,10 @@
1
+ {% assign first_doc = site.documents | first %}
2
+ {% assign icon_set = first_doc.icon_set | default: 'color' %}
1
3
  {% assign cats = site.documents | map: "category" | uniq | sort %}
2
4
  <ul class="documents-category-list">
3
5
  {% for cat in cats %}
4
6
  <li>
5
- <img src="/assets/icons/{{ site.documents.icon_set | default: 'color' }}/folder-svgrepo-com.svg" alt="Folder" class="folder-icon" />
7
+ <img src="{{ '/assets/icons/' | append: icon_set | append: '/folder-svgrepo-com.svg' | relative_url }}" alt="Folder" class="folder-icon" />
6
8
  <span class="category-name">{{ cat }}</span>
7
9
  <span class="category-count">({{ site.documents | where: "category", cat | size }})</span>
8
10
  </li>
@@ -2,7 +2,7 @@
2
2
  <ul class="documents-list">
3
3
  {% for doc in docs %}
4
4
  <li>
5
- {{ doc.file_type | file_type_icon_tag }}
5
+ <img src="{{ doc.icon_url | relative_url }}" alt="{{ doc.file_type | upcase }} file" class="file-icon" />
6
6
  <a href="{{ doc.url | relative_url }}">{{ doc.title }}</a>
7
7
  <small>({{ doc.date | date: "%Y-%m-%d" }}, {{ doc.category }})</small>
8
8
  </li>
@@ -2,3 +2,5 @@
2
2
  <input id="doc-search-input" type="search" placeholder="Search documents…" aria-label="Search documents" />
3
3
  <div id="doc-search-results" class="documents-search-results"></div>
4
4
  </div>
5
+ <script src="https://unpkg.com/lunr/lunr.js"></script>
6
+ <script src="{{ '/assets/js/documents-search.js' | relative_url }}"></script>
@@ -1,9 +1,9 @@
1
- {% assign count = include.count | default: site.config.documents.latest_default_count | default: 5 %}
1
+ {% assign count = include.count | default: 5 %}
2
2
  {% assign docs = site.documents | sort: "date" | reverse | slice: 0, count %}
3
3
  <ul class="latest-documents">
4
4
  {% for doc in docs %}
5
5
  <li>
6
- {{ doc.file_type | file_type_icon_tag }}
6
+ <img src="{{ doc.icon_url | relative_url }}" alt="{{ doc.file_type | upcase }} file" class="file-icon" />
7
7
  <a href="{{ doc.url | relative_url }}">{{ doc.title }}</a>
8
8
  <small>({{ doc.date | date: "%Y-%m-%d" }}, {{ doc.category }})</small>
9
9
  </li>
@@ -13,7 +13,7 @@ layout: default
13
13
 
14
14
  <p>
15
15
  <a href="{{ page.file_url | relative_url }}" class="document-download">
16
- {{ page.file_type | file_type_icon_tag }}
16
+ <img src="{{ page.icon_url | relative_url }}" alt="{{ page.file_type | upcase }} file" class="file-icon" />
17
17
  Download ({{ page.extension | remove: "." | upcase }})
18
18
  </a>
19
19
  </p>
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  "rubygems_mfa_required" => "true"
26
26
  }
27
27
 
28
- spec.required_ruby_version = ">= 2.7"
28
+ spec.required_ruby_version = ">= 3.1"
29
29
 
30
30
  spec.files = Dir.glob("{lib,assets,_includes,_layouts}/**/*") +
31
31
  ["README.md", "CHANGELOG.md", "LICENSE", "jekyll-documents.gemspec"]
@@ -39,6 +39,6 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency "rspec", "~> 3.12"
40
40
  spec.add_development_dependency "rubocop", "~> 1.60"
41
41
  spec.add_development_dependency "rubocop-performance", "~> 1.20"
42
- spec.add_development_dependency "simplecov", "~> 0.22"
42
+ spec.add_development_dependency "simplecov", "~> 1.0"
43
43
  spec.add_development_dependency "yard", "~> 0.9"
44
44
  end
@@ -95,6 +95,18 @@ module Jekyll
95
95
  }
96
96
  }.freeze
97
97
 
98
+ # Returns the icon URL for a given file type and explicit icon set
99
+ # @param file_type [String] the file extension (e.g., 'pdf', 'docx')
100
+ # @param icon_set [String] the icon set name (defaults to 'color')
101
+ # @return [String] the icon file URL
102
+ # @example
103
+ # FileTypeIcons.icon_for('pdf', 'lines') #=> "/assets/icons/lines/pdf-svgrepo-com.svg"
104
+ def self.icon_for(file_type, icon_set = "color")
105
+ icons = ICON_MAP[icon_set] || ICON_MAP["color"]
106
+ icons[file_type.to_s.downcase] ||
107
+ "/assets/icons/#{icon_set}/unknown-document-svgrepo-com.svg"
108
+ end
109
+
98
110
  # Returns the icon URL for a given file type using configured icon set
99
111
  # @param file_type [String] the file extension (e.g., 'pdf', 'docx')
100
112
  # @param context [Liquid::Context] the Liquid context for accessing site config
@@ -102,10 +114,7 @@ module Jekyll
102
114
  # @example
103
115
  # file_type_icon('pdf') #=> "/assets/icons/color/pdf.svg"
104
116
  def file_type_icon(file_type, context = nil)
105
- icon_set = get_icon_set(context)
106
- icons = ICON_MAP[icon_set] || ICON_MAP["color"]
107
- icons[file_type.to_s.downcase] ||
108
- "/assets/icons/#{icon_set}/unknown-document-svgrepo-com.svg"
117
+ Jekyll::Documents::FileTypeIcons.icon_for(file_type, get_icon_set(context))
109
118
  end
110
119
 
111
120
  # Returns an HTML img tag for the file type icon
@@ -119,7 +128,7 @@ module Jekyll
119
128
  # '<img src="/assets/icons/color/pdf.svg" alt="PDF file" class="file-icon" />'
120
129
  def file_type_icon_tag(file_type, css_class: "file-icon", alt: nil, context: nil)
121
130
  url = file_type_icon(file_type, context)
122
- alt_text = alt || "#{file_type.upcase} file"
131
+ alt_text = alt || "#{file_type.to_s.upcase} file"
123
132
  %(<img src="#{url}" alt="#{alt_text}" class="#{css_class}" />)
124
133
  end
125
134
 
@@ -131,11 +140,8 @@ module Jekyll
131
140
  def get_icon_set(context)
132
141
  return "color" unless context
133
142
 
134
- site_config = context.registers[:site]&.config
135
- documents_config = site_config["documents"] if site_config
136
- icon_set = documents_config["icon_set"] if documents_config
137
-
138
- # Validate icon set exists
143
+ site = context.registers[:site]
144
+ icon_set = site&.config&.dig("documents", "icon_set")
139
145
  return "color" unless icon_set && ICON_MAP.key?(icon_set)
140
146
 
141
147
  icon_set
@@ -10,17 +10,18 @@ module Jekyll
10
10
 
11
11
  # Slugify while handling Danish letters and basic punctuation cleanup.
12
12
  def documents_slugify(input, downcase: true, danish_map: true)
13
- s = input.to_s.dup
14
- s = s.gsub(/[æøåÆØÅ]/) { |ch| DANISH_MAP[ch] } if danish_map
15
- s = s.strip.gsub(/[^\p{Alnum}\-_\s]/u, "")
16
- s = s.tr(" ", "-").squeeze("-")
17
- s = s.gsub(/[æøåÆØÅ]/, "") unless danish_map # Remove Danish chars if mapping disabled
18
- s = s.downcase if downcase
19
- s
13
+ result = input.to_s.dup
14
+ result = result.gsub(/[æøåÆØÅ]/) { |ch| DANISH_MAP[ch] } if danish_map
15
+ result = result.strip.gsub(/[^\p{Alnum}\-_\s]/u, "")
16
+ result = result.tr(" ", "-").squeeze("-")
17
+ result = result.gsub(/[æøåÆØÅ]/, "") unless danish_map
18
+ result = result.downcase if downcase
19
+ result
20
20
  end
21
21
 
22
22
  # Title from filename e.g. "2026-03-01_Title_Here" -> "Title Here"
23
23
  def documents_title_from_filename(basename)
24
+ basename = basename.to_s
24
25
  if basename =~ /^\d{4}-\d{2}-\d{2}_(.+)$/
25
26
  Regexp.last_match(1).tr("_", " ")
26
27
  else
@@ -35,7 +35,7 @@ module Jekyll
35
35
  end
36
36
  next unless @config["include_extensions"].include?(ext)
37
37
 
38
- rel_path = path.start_with?(site.source) ? path[(site.source.length + 1)..] : path
38
+ rel_path = path.delete_prefix("#{site.source}/")
39
39
  category = infer_category_from(rel_path)
40
40
  basename = File.basename(path, ext)
41
41
 
@@ -46,6 +46,8 @@ module Jekyll
46
46
  end
47
47
 
48
48
  slug = build_slug(basename)
49
+ file_type = ext.sub(".", "").downcase
50
+ icon_set = @config["icon_set"]
49
51
 
50
52
  doc = ::Jekyll::Document.new(
51
53
  source_stub_for(basename, category),
@@ -53,17 +55,20 @@ module Jekyll
53
55
  collection: collection
54
56
  )
55
57
 
56
- doc.data["layout"] = @config["layout"]
57
- doc.data["title"] = title
58
- doc.data["date"] = date || File.mtime(path)
59
- doc.data["category"] = remap_category(category)
60
- doc.data["file_url"] = "/#{rel_path}"
61
- doc.data["extension"] = ext
62
- doc.data["file_type"] = ext.sub(".", "").downcase
63
- doc.data["slug"] = slug
64
- doc.data["permalink"] = @config["permalink"]
65
- .gsub(":category", doc.data["category"].to_s)
66
- .gsub(":slug", slug)
58
+ data = doc.data
59
+ data["layout"] = @config["layout"]
60
+ data["title"] = title
61
+ data["date"] = date || File.mtime(path)
62
+ data["category"] = remap_category(category)
63
+ data["file_url"] = "/#{rel_path}"
64
+ data["extension"] = ext
65
+ data["file_type"] = file_type
66
+ data["icon_set"] = icon_set
67
+ data["icon_url"] = Jekyll::Documents::FileTypeIcons.icon_for(file_type, icon_set)
68
+ data["slug"] = slug
69
+ data["permalink"] = @config["permalink"]
70
+ .gsub(":category", data["category"].to_s)
71
+ .gsub(":slug", slug)
67
72
 
68
73
  doc.content = "Auto-generated document page."
69
74
 
@@ -117,8 +122,10 @@ module Jekyll
117
122
  # @return [Array<Date, String, Boolean>] date, title, and validity flag
118
123
  def parse_filename(basename)
119
124
  if basename =~ /^(\d{4})-(\d{2})-(\d{2})_(.+)$/
120
- date = Date.parse("#{Regexp.last_match(1)}-#{Regexp.last_match(2)}-" \
121
- "#{Regexp.last_match(3)}")
125
+ year = Regexp.last_match(1).to_i
126
+ month = Regexp.last_match(2).to_i
127
+ day = Regexp.last_match(3).to_i
128
+ date = Date.new(year, month, day)
122
129
  title = Regexp.last_match(4).tr("_", " ")
123
130
  [date, title, true]
124
131
  else
@@ -132,16 +139,16 @@ module Jekyll
132
139
  # @param basename [String] the filename without extension
133
140
  # @return [String] the generated slug
134
141
  def build_slug(basename)
135
- s = basename.sub(/^\d{4}-\d{2}-\d{2}_/, "")
142
+ slug = basename.sub(/^\d{4}-\d{2}-\d{2}_/, "")
136
143
  if @config["slug_danish_map"]
137
- s = s.gsub(/[æøåÆØÅ]/,
138
- { "æ" => "ae", "ø" => "oe", "å" => "aa", "Æ" => "Ae", "Ø" => "Oe",
139
- "Å" => "Aa" })
144
+ slug = slug.gsub(/[æøåÆØÅ]/,
145
+ { "æ" => "ae", "ø" => "oe", "å" => "aa", "Æ" => "Ae", "Ø" => "Oe",
146
+ "Å" => "Aa" })
140
147
  end
141
- s = s.downcase if @config["slug_downcase"]
142
- s = s.gsub(/[^\p{Alnum}\-_\s]/u, "").tr("_ ", "--").squeeze("-")
143
- s = s.sub(/^-+/, "").sub(/-+$/, "") # Remove leading/trailing hyphens
144
- s.empty? ? "untitled" : s
148
+ slug = slug.downcase if @config["slug_downcase"]
149
+ slug = slug.gsub(/[^\p{Alnum}\-_\s]/u, "").tr("_ ", "--").squeeze("-")
150
+ slug = slug.sub(/^-+/, "").sub(/-+$/, "")
151
+ slug.empty? ? "untitled" : slug
145
152
  end
146
153
  end
147
154
  end
@@ -16,15 +16,16 @@ module Jekyll
16
16
  docs = site.collections["documents"]&.docs || []
17
17
  return if docs.empty?
18
18
 
19
- items = docs.map do |d|
19
+ items = docs.map do |doc|
20
+ data = doc.data
20
21
  {
21
- "url" => d.url,
22
- "title" => d.data["title"],
23
- "category" => d.data["category"],
24
- "date" => (d.data["date"] || Time.at(0)).strftime("%Y-%m-%d"),
25
- "slug" => d.data["slug"],
26
- "file_type" => d.data["file_type"],
27
- "extension" => d.data["extension"]
22
+ "url" => doc.url,
23
+ "title" => data["title"],
24
+ "category" => data["category"],
25
+ "date" => (data["date"] || Time.at(0)).strftime("%Y-%m-%d"),
26
+ "slug" => data["slug"],
27
+ "file_type" => data["file_type"],
28
+ "extension" => data["extension"]
28
29
  }
29
30
  end
30
31
 
@@ -17,14 +17,15 @@ module Jekyll
17
17
  category = @args["category"]
18
18
 
19
19
  docs = site.collections["documents"]&.docs || []
20
- docs = docs.select { |d| d.data["category"] == category } if category
21
- docs = docs.sort_by { |d| d.data["date"] || Time.at(0) }.reverse.first(count)
20
+ docs = docs.select { |doc| doc.data["category"] == category } if category
21
+ docs = docs.sort_by { |doc| doc.data["date"] || Time.at(0) }.reverse.first(count)
22
22
 
23
23
  out = +"<ul class=\"latest-documents\">\n"
24
- docs.each do |d|
25
- title = escape_html(d.data["title"])
26
- url = escape_html(d.url)
27
- date = (d.data["date"] || Time.at(0)).strftime("%Y-%m-%d")
24
+ docs.each do |doc|
25
+ data = doc.data
26
+ title = escape_html(data["title"])
27
+ url = escape_html(doc.url)
28
+ date = (data["date"] || Time.at(0)).strftime("%Y-%m-%d")
28
29
  out << %(<li><a href="#{url}">#{title}</a> <small>(#{date})</small></li>\n)
29
30
  end
30
31
  out << "</ul>\n"
@@ -50,11 +51,11 @@ module Jekyll
50
51
  args = {}
51
52
  # Safe: markup comes from Jekyll template authors (trusted), not end users
52
53
  # Runs only during static site generation, not on user requests
53
- markup.scan(/(\w+)\s*:\s*'([^']*)'|(\w+)\s*:\s*([^\s]+)/).each do |m|
54
- if m[0]
55
- args[m[0]] = m[1]
54
+ markup.scan(/(\w+)\s*:\s*'([^']*)'|(\w+)\s*:\s*([^\s]+)/).each do |match|
55
+ if match[0]
56
+ args[match[0]] = match[1]
56
57
  else
57
- args[m[2]] = m[3]
58
+ args[match[2]] = match[3]
58
59
  end
59
60
  end
60
61
  args
@@ -4,12 +4,15 @@ module Jekyll
4
4
  module Documents
5
5
  # A tiny StaticFile subclass for writing text files during build.
6
6
  class TextStaticFile < ::Jekyll::StaticFile
7
+ attr_reader :data
8
+
7
9
  def initialize(site, base, rel_dest, content)
8
10
  @site = site
9
11
  @base = base
10
12
  @dir = File.dirname(rel_dest)
11
13
  @name = File.basename(rel_dest)
12
14
  @content = content
15
+ @data = { "sitemap" => false }
13
16
  super(site, base, @dir, @name)
14
17
  end
15
18
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module Documents
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-documents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svend Gundestrup
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0.22'
117
+ version: '1.0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0.22'
124
+ version: '1.0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: yard
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -279,7 +279,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
279
279
  requirements:
280
280
  - - ">="
281
281
  - !ruby/object:Gem::Version
282
- version: '2.7'
282
+ version: '3.1'
283
283
  required_rubygems_version: !ruby/object:Gem::Requirement
284
284
  requirements:
285
285
  - - ">="