jekyll-documents 0.3.2 → 0.3.3

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: 2c05b94feef92602870b24fd7b9c4e9244eb724061c1ef599ed12a23905d51ad
4
- data.tar.gz: ccfe08306d471478e21092fd7518df1c6d593b05fa219051a6d866d3c947c6c9
3
+ metadata.gz: b2e673dc309679ea3bc1ce25687bb9fbf52901f27b7b29517c63ef9337291e82
4
+ data.tar.gz: f89cef0e3d22c85665b516bd52da7f137b612c62c21f24e22ef33dc7a10170a8
5
5
  SHA512:
6
- metadata.gz: '087a677f0f1efd8c54d7b52c05c1fd0b88c746e9063f8fc0ba843c5ca24db53b8cbdd6904a959d7ca606e490bf8da590ff9b400b21a9de25204079ed0f0b0eff'
7
- data.tar.gz: 7519b8f1e7035b4391299dd6399dcb1a6fa6572d4b3567891e35fc3f383a7b2958acc28bdbfe058983d6fbf73592c33a609dd07ae082751410873d8d29ac1434
6
+ metadata.gz: 956c33415793dd9e285f9e81874b612278c188d0dd39237a1bbdeea7964af1d784655b171d7efbf485b37dfa948c02ade3703b4e82e1b411a90d9c2049cb0f65
7
+ data.tar.gz: c6069125baef1f06acbd58124f5993881b2e595a60b02e470fb0a8a3a94a764f62b0bbd8ad53320b325d614590aa494fb469f83674145f1924db945e1afc0e46
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.3] - 2026-08-12
6
+
7
+ ### Added
8
+ - `{% doc_link "title" %}` Liquid tag for linking to documents by partial title or slug match (case-insensitive); renders file icon, title, and human-readable file size; supports `text:"custom label"`, `icon:false`, and `size:false` options
9
+ - `{% doc_category "name" %}` Liquid tag for linking to category pages or listing documents by category; supports `text:"label"`, `list:true`, and `limit:N` options
10
+ - `file_size` attribute baked into each document's data at generation time
11
+ - `file_url` and `file_size` fields included in the generated `/documents.json` index
12
+ - 30 RSpec examples covering `DocLinkTag` and `DocCategoryTag` (exact/partial matching, slug matching, icon/size rendering, custom text, baseurl, HTML escaping, list mode, limit, sorting, empty collection handling)
13
+
5
14
  ## [0.3.2] - 2026-08-12
6
15
 
7
16
  ### Added
data/README.md CHANGED
@@ -74,12 +74,41 @@ The tag applies the configured icon set and site `baseurl` automatically. The `f
74
74
 
75
75
  ## Usage
76
76
 
77
+ ### Includes
78
+
77
79
  ```liquid
78
80
  {% include latest_documents.html count=5 %}
79
81
  {% include documents_list.html %}
80
82
  {% include documents_search.html %}
81
83
  ```
82
84
 
85
+ ### Liquid Tags
86
+
87
+ **Link to a document** by title or slug (partial match, case-insensitive):
88
+
89
+ ```liquid
90
+ {% doc_link "Annual Report" %}
91
+ {% doc_link "annual" text:"Read the report" %}
92
+ {% doc_link "board-meeting" icon:false size:false %}
93
+ ```
94
+
95
+ Renders an `<a>` tag with the file type icon, title, and human-readable file size.
96
+ Use `text:"..."` to override the link text, `icon:false` to hide the icon,
97
+ or `size:false` to hide the file size.
98
+
99
+ **Link to or list a category**:
100
+
101
+ ```liquid
102
+ {% doc_category "reports" %}
103
+ {% doc_category "reports" text:"All reports" %}
104
+ {% doc_category "reports" list:true %}
105
+ {% doc_category "reports" list:true limit:5 %}
106
+ ```
107
+
108
+ Link mode renders an `<a>` tag to the category page.
109
+ List mode renders a `<ul>` of all documents in the category, sorted by date descending.
110
+ Use `limit:N` to cap the number of items.
111
+
83
112
  ## Configuration
84
113
 
85
114
  ```yaml
@@ -65,6 +65,7 @@ module Jekyll
65
65
  data["file_type"] = file_type
66
66
  data["icon_set"] = icon_set
67
67
  data["icon_url"] = Jekyll::Documents::FileTypeIcons.icon_for(file_type, icon_set)
68
+ data["file_size"] = File.size(path)
68
69
  data["slug"] = slug
69
70
  data["permalink"] = @config["permalink"]
70
71
  .gsub(":category", data["category"].to_s)
@@ -26,7 +26,9 @@ module Jekyll
26
26
  "slug" => data["slug"],
27
27
  "file_type" => data["file_type"],
28
28
  "extension" => data["extension"],
29
- "icon_url" => data["icon_url"]
29
+ "icon_url" => data["icon_url"],
30
+ "file_url" => data["file_url"],
31
+ "file_size" => data["file_size"]
30
32
  }
31
33
  end
32
34
 
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Documents
5
+ class DocCategoryTag < Liquid::Tag
6
+ public_class_method :new
7
+
8
+ include Jekyll::Filters::URLFilters
9
+
10
+ def initialize(tag_name, markup, tokens)
11
+ super
12
+ @category, @options = parse_markup(markup)
13
+ end
14
+
15
+ def render(context)
16
+ docs = documents_from(context)
17
+ return "" if docs.empty?
18
+
19
+ categories = available_categories(docs)
20
+ category = resolve_category(@category, categories)
21
+ return "" unless category
22
+
23
+ @context = context
24
+ return render_list(docs, category) if @options["list"] == "true"
25
+
26
+ render_link(category)
27
+ end
28
+
29
+ private
30
+
31
+ def documents_from(context)
32
+ site = context.registers[:site]
33
+ site.collections["documents"]&.docs || []
34
+ end
35
+
36
+ def available_categories(docs)
37
+ docs.map { |doc| doc.data["category"].to_s }.uniq.sort
38
+ end
39
+
40
+ def resolve_category(query, categories)
41
+ normalized = query.to_s.strip.downcase
42
+ return nil if normalized.empty?
43
+
44
+ categories.find { |cat| cat.downcase == normalized } ||
45
+ categories.find { |cat| cat.downcase.include?(normalized) }
46
+ end
47
+
48
+ def render_link(category)
49
+ url = relative_url("/documents/#{category}/")
50
+ text = @options["text"] || category
51
+ %(<a href="#{escape_html(url)}">#{escape_html(text)}</a>)
52
+ end
53
+
54
+ def render_list(docs, category)
55
+ cat_docs = sorted_category_docs(docs, category)
56
+ limit = @options["limit"]&.to_i
57
+ cat_docs = cat_docs.first(limit) if limit&.positive?
58
+
59
+ out = %(<ul class="doc-category-list" data-category="#{escape_html(category)}">\n)
60
+ out << list_items(cat_docs)
61
+ out << "</ul>\n"
62
+ end
63
+
64
+ def sorted_category_docs(docs, category)
65
+ docs.select { |doc| doc.data["category"].to_s == category }
66
+ .sort_by { |doc| doc.data["date"] || Time.at(0) }.reverse
67
+ end
68
+
69
+ def list_items(cat_docs)
70
+ cat_docs.each_with_object(+"") do |doc, out|
71
+ title = escape_html(doc.data["title"])
72
+ url = escape_html(relative_url(doc.url))
73
+ date = (doc.data["date"] || Time.at(0)).strftime("%Y-%m-%d")
74
+ out << %(<li><a href="#{url}">#{title}</a> <small>(#{date})</small></li>\n)
75
+ end
76
+ end
77
+
78
+ def parse_markup(markup)
79
+ text = markup.to_s
80
+ category, remainder = extract_category(text)
81
+ options = extract_options(remainder)
82
+ [category, options]
83
+ end
84
+
85
+ def extract_category(text)
86
+ quoted = text.match(/\A["']([^"']+)["']/)
87
+ return [quoted[1], text[quoted.end(0)..]] if quoted
88
+
89
+ bare = text.strip.match(/\A([^\s]+)/)
90
+ return [nil, ""] unless bare
91
+
92
+ [bare[1], text[bare.end(0)..]]
93
+ end
94
+
95
+ def extract_options(text)
96
+ options = {}
97
+ text.to_s.scan(
98
+ /(\w+)\s*:\s*(?:'([^']*)'|"([^"]*)"|([^\s]+))/
99
+ ) do |key, single, double, bare|
100
+ options[key] = single || double || bare
101
+ end
102
+ options
103
+ end
104
+
105
+ def escape_html(value)
106
+ value.to_s.gsub(/[&<>"']/,
107
+ "&" => "&amp;",
108
+ "<" => "&lt;",
109
+ ">" => "&gt;",
110
+ '"' => "&quot;",
111
+ "'" => "&#39;")
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ Liquid::Template.register_tag("doc_category", Jekyll::Documents::DocCategoryTag)
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Documents
5
+ class DocLinkTag < Liquid::Tag
6
+ public_class_method :new
7
+
8
+ include Jekyll::Filters::URLFilters
9
+
10
+ SIZE_UNITS = %w[B KB MB GB].freeze
11
+
12
+ def initialize(tag_name, markup, tokens)
13
+ super
14
+ @query, @options = parse_markup(markup)
15
+ end
16
+
17
+ def render(context)
18
+ docs = documents_from(context)
19
+ return "" if docs.empty?
20
+
21
+ match = find_document(docs, @query)
22
+ return "" unless match
23
+
24
+ @context = context
25
+ url = relative_url(match.url)
26
+ text = @options["text"] || match.data["title"]
27
+
28
+ build_link(match, url, text)
29
+ end
30
+
31
+ private
32
+
33
+ def documents_from(context)
34
+ site = context.registers[:site]
35
+ site.collections["documents"]&.docs || []
36
+ end
37
+
38
+ def find_document(docs, query)
39
+ normalized = query.to_s.strip.downcase
40
+ return nil if normalized.empty?
41
+
42
+ docs.find { |doc| matches?(doc, normalized) }
43
+ end
44
+
45
+ def matches?(doc, query)
46
+ title = doc.data["title"].to_s.downcase
47
+ slug = doc.data["slug"].to_s.downcase
48
+ title == query || slug == query ||
49
+ title.include?(query) || slug.include?(query)
50
+ end
51
+
52
+ def build_link(doc, url, text)
53
+ inner = +""
54
+ inner << icon_html(doc) if @options["icon"] != "false"
55
+ inner << %(<span class="doc-link-title">#{escape_html(text)}</span>)
56
+ inner << size_html(doc) if @options["size"] != "false"
57
+ %(<a href="#{escape_html(url)}" class="doc-link">#{inner}</a>)
58
+ end
59
+
60
+ def icon_html(doc)
61
+ icon_url = doc.data["icon_url"]
62
+ return "" unless icon_url
63
+
64
+ url = relative_url(icon_url)
65
+ file_type = doc.data["file_type"].to_s.upcase
66
+ "<img src=\"#{escape_html(url)}\" alt=\"#{file_type}\" class=\"file-icon doc-link-icon\" />"
67
+ end
68
+
69
+ def size_html(doc)
70
+ size = doc.data["file_size"]
71
+ return "" unless size
72
+
73
+ "<span class=\"doc-link-size has-text-grey is-size-7\">#{format_size(size)}</span>"
74
+ end
75
+
76
+ def format_size(bytes)
77
+ return "0 B" unless bytes&.positive?
78
+
79
+ value, unit = compute_size(bytes)
80
+ unit == "B" ? "#{value.to_i} B" : format("%<value>.1f %<unit>s", value:, unit:)
81
+ end
82
+
83
+ def compute_size(bytes)
84
+ value = bytes.to_f
85
+ SIZE_UNITS.each do |unit|
86
+ return [value, unit] if value < 1024 || unit == SIZE_UNITS.last
87
+
88
+ value /= 1024
89
+ end
90
+ end
91
+
92
+ def parse_markup(markup)
93
+ text = markup.to_s
94
+ query, remainder = extract_query(text)
95
+ options = extract_options(remainder)
96
+ [query, options]
97
+ end
98
+
99
+ def extract_query(text)
100
+ quoted = text.match(/\A["']([^"']+)["']/)
101
+ return [quoted[1], text[quoted.end(0)..]] if quoted
102
+
103
+ bare = text.strip.match(/\A([^\s]+)/)
104
+ return [nil, ""] unless bare
105
+
106
+ [bare[1], text[bare.end(0)..]]
107
+ end
108
+
109
+ def extract_options(text)
110
+ options = {}
111
+ text.to_s.scan(
112
+ /(\w+)\s*:\s*(?:'([^']*)'|"([^"]*)"|([^\s]+))/
113
+ ) do |key, single, double, bare|
114
+ options[key] = single || double || bare
115
+ end
116
+ options
117
+ end
118
+
119
+ def escape_html(value)
120
+ value.to_s.gsub(/[&<>"']/,
121
+ "&" => "&amp;",
122
+ "<" => "&lt;",
123
+ ">" => "&gt;",
124
+ '"' => "&quot;",
125
+ "'" => "&#39;")
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ Liquid::Template.register_tag("doc_link", Jekyll::Documents::DocLinkTag)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module Documents
5
- VERSION = "0.3.2"
5
+ VERSION = "0.3.3"
6
6
  end
7
7
  end
@@ -14,6 +14,8 @@ require_relative "jekyll/documents/layout_registrar"
14
14
  # Liquid tag(s)
15
15
  require_relative "jekyll/documents/tags/latest_documents"
16
16
  require_relative "jekyll/documents/tags/document_icon"
17
+ require_relative "jekyll/documents/tags/doc_link"
18
+ require_relative "jekyll/documents/tags/doc_category"
17
19
 
18
20
  module Jekyll
19
21
  module Documents
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-documents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svend Gundestrup
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-12 00:00:00.000000000 Z
11
+ date: 2026-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -266,6 +266,8 @@ files:
266
266
  - lib/jekyll/documents/generator.rb
267
267
  - lib/jekyll/documents/json_index_generator.rb
268
268
  - lib/jekyll/documents/layout_registrar.rb
269
+ - lib/jekyll/documents/tags/doc_category.rb
270
+ - lib/jekyll/documents/tags/doc_link.rb
269
271
  - lib/jekyll/documents/tags/document_icon.rb
270
272
  - lib/jekyll/documents/tags/latest_documents.rb
271
273
  - lib/jekyll/documents/utils.rb