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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +29 -0
- data/lib/jekyll/documents/generator.rb +1 -0
- data/lib/jekyll/documents/json_index_generator.rb +3 -1
- data/lib/jekyll/documents/tags/doc_category.rb +117 -0
- data/lib/jekyll/documents/tags/doc_link.rb +131 -0
- data/lib/jekyll/documents/version.rb +1 -1
- data/lib/jekyll-documents.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2e673dc309679ea3bc1ce25687bb9fbf52901f27b7b29517c63ef9337291e82
|
|
4
|
+
data.tar.gz: f89cef0e3d22c85665b516bd52da7f137b612c62c21f24e22ef33dc7a10170a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
+
"&" => "&",
|
|
108
|
+
"<" => "<",
|
|
109
|
+
">" => ">",
|
|
110
|
+
'"' => """,
|
|
111
|
+
"'" => "'")
|
|
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
|
+
"&" => "&",
|
|
122
|
+
"<" => "<",
|
|
123
|
+
">" => ">",
|
|
124
|
+
'"' => """,
|
|
125
|
+
"'" => "'")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
Liquid::Template.register_tag("doc_link", Jekyll::Documents::DocLinkTag)
|
data/lib/jekyll-documents.rb
CHANGED
|
@@ -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.
|
|
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-
|
|
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
|