jekyll-documents 0.2.0 → 0.3.2
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 +73 -0
- data/README.md +24 -4
- data/_includes/category_list.html +3 -1
- data/_includes/documents_list.html +1 -1
- data/_includes/documents_search.html +10 -0
- data/_includes/latest_documents.html +2 -2
- data/_layouts/document.html +1 -1
- data/assets/js/documents-search.js +68 -90
- data/jekyll-documents.gemspec +7 -7
- data/lib/jekyll/documents/assets_generator.rb +48 -0
- data/lib/jekyll/documents/file_type_icons.rb +79 -44
- data/lib/jekyll/documents/filters.rb +8 -7
- data/lib/jekyll/documents/generator.rb +29 -22
- data/lib/jekyll/documents/json_index_generator.rb +10 -8
- data/lib/jekyll/documents/layout_registrar.rb +60 -0
- data/lib/jekyll/documents/tags/document_icon.rb +68 -0
- data/lib/jekyll/documents/tags/latest_documents.rb +11 -10
- data/lib/jekyll/documents/utils.rb +3 -0
- data/lib/jekyll/documents/version.rb +1 -1
- data/lib/jekyll-documents.rb +3 -0
- metadata +25 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c05b94feef92602870b24fd7b9c4e9244eb724061c1ef599ed12a23905d51ad
|
|
4
|
+
data.tar.gz: ccfe08306d471478e21092fd7518df1c6d593b05fa219051a6d866d3c947c6c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '087a677f0f1efd8c54d7b52c05c1fd0b88c746e9063f8fc0ba843c5ca24db53b8cbdd6904a959d7ca606e490bf8da590ff9b400b21a9de25204079ed0f0b0eff'
|
|
7
|
+
data.tar.gz: 7519b8f1e7035b4391299dd6399dcb1a6fa6572d4b3567891e35fc3f383a7b2958acc28bdbfe058983d6fbf73592c33a609dd07ae082751410873d8d29ac1434
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.3.2] - 2026-08-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Auto-registration of gem-packaged `_layouts` and `_includes` via a `:site, :after_init` hook so Jekyll discovers the `document` layout and all includes without the gem being declared as a theme; user-provided files always take precedence
|
|
9
|
+
- 7 RSpec examples for `LayoutRegistrar` covering copy, override, idempotency, and hook registration
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Corrected publish workflow Ruby version from 3.2 to 3.3 to match the gemspec `required_ruby_version` so the gem builds and publishes to RubyGems successfully
|
|
13
|
+
|
|
14
|
+
## [0.3.1] - 2026-08-03
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Added the context-aware `{% document_icon page %}` Liquid tag for simple icon rendering
|
|
18
|
+
- Added installed-gem system coverage for all supported document types and icon themes
|
|
19
|
+
- Added Playwright browser coverage for search interaction, links, icons, baseurl, and network errors
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Minimum Ruby version raised from `>= 3.2` to `>= 3.3`
|
|
23
|
+
- Updated RuboCop target Ruby version to 3.3
|
|
24
|
+
- Updated compatible development gems, including RuboCop, Reek, RSpec, and Playwright tooling
|
|
25
|
+
- Test and quality tasks now rebuild and force-install the gem before testing
|
|
26
|
+
- Added package-content checks to verify the built gem contains runtime templates, icons, JavaScript, and Liquid tags
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Corrected non-color icon mappings to reference packaged SVG assets
|
|
30
|
+
- Registered packaged icons and search JavaScript for copying during Jekyll builds
|
|
31
|
+
- Made document search respect the configured index path and site base URL
|
|
32
|
+
- Hardened document search index validation and disabled the search include when JSON indexing is disabled
|
|
33
|
+
- Escaped search-result URLs, icon URLs, titles, categories, and dates before rendering
|
|
34
|
+
- Added the missing date conversion required for Jekyll 4.4 document publishing
|
|
35
|
+
- Removed duplicate search scripts from the example site
|
|
36
|
+
- Made Reek failures fail the quality task consistently
|
|
37
|
+
|
|
38
|
+
### Verification
|
|
39
|
+
- 186 Ruby/system examples passing with 100% Ruby line coverage
|
|
40
|
+
- 3 Playwright browser tests passing
|
|
41
|
+
- 0 RuboCop offenses, 0 Reek warnings, and 0 Bundler Audit vulnerabilities
|
|
42
|
+
|
|
43
|
+
## [0.3.0] - 2026-07-23
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- **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
|
|
47
|
+
- **`FileTypeIcons.icon_for` class method**: Allows icon URL resolution without a Liquid context (used by generator)
|
|
48
|
+
- **Search JavaScript wired up**: `_includes/documents_search.html` now loads Lunr.js and `documents-search.js` for out-of-the-box client-side search
|
|
49
|
+
- **jekyll-sitemap integration**: `TextStaticFile` exposes `sitemap: false` data to exclude generated JSON index from sitemap
|
|
50
|
+
- **Unit tests for `FileTypeIcons.icon_for`**: 5 new tests covering all icon sets, fallbacks, and nil handling
|
|
51
|
+
- **Generator tests for icon data**: Tests verifying `icon_url` and `icon_set` are correctly baked into document data
|
|
52
|
+
- **System test for full build pipeline**: End-to-end test covering generator → JSON index → tag rendering
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
- **Minimum Ruby version**: Bumped from `>= 2.7` to `>= 3.2` (zeitwerk 2.7+ — a Jekyll dependency — requires Ruby >= 3.2)
|
|
56
|
+
- **RuboCop `TargetRubyVersion`**: Updated from `2.7` to `3.2` to match gemspec
|
|
57
|
+
- **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
|
|
58
|
+
- **`category_list.html` icon set**: Now reads `icon_set` from first document's baked data instead of non-functional `site.documents.icon_set`
|
|
59
|
+
- **`latest_documents.html` count fallback**: Simplified to `| default: 5` since `site.config` is inaccessible in Liquid
|
|
60
|
+
- **Reek configuration**: Updated `.reek.yml` with targeted exclusions for design-inherent warnings
|
|
61
|
+
- **Code quality**: Renamed uncommunicative variables (`s` → `slug`/`result`, `d` → `doc`, `m` → `match`, `y/m/d` → `year/month/day`) across all source files
|
|
62
|
+
- **Reduced duplicate method calls**: Extracted `data = doc.data` locals in generator, JSON index generator, and latest documents tag
|
|
63
|
+
- **Simplified `get_icon_set`**: Reduced from 6 statements to 3 using safe navigation with `dig`
|
|
64
|
+
- **Test suite**: 167 examples (up from 79), 100% line coverage (212/212 lines)
|
|
65
|
+
- **RuboCop**: 0 offenses (24 files inspected)
|
|
66
|
+
- **Reek**: 0 warnings (7 files inspected)
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
- **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
|
|
70
|
+
- **`rel_path` slicing** (Low): Replaced fragile `path[(site.source.length + 1)..]` with `String#delete_prefix` for robustness
|
|
71
|
+
- **`Date.parse` in `parse_filename`** (Low): Replaced with `Date.new(year, month, day)` using integer-converted regex captures for clarity
|
|
72
|
+
- **`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
|
|
73
|
+
- **Search include missing JavaScript** (Medium): `documents_search.html` had input/results elements but no script tags — now loads Lunr.js and search JS
|
|
74
|
+
- **No active jekyll-sitemap integration** (Low): JSON index file now flagged with `sitemap: false` to exclude it from sitemap
|
|
75
|
+
|
|
3
76
|
## [0.2.0] - 2026-03-12
|
|
4
77
|
|
|
5
78
|
### Added
|
data/README.md
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
# jekyll-documents
|
|
2
|
+
[](https://github.com/gundestrup/jekyll-documents)
|
|
3
|
+
[](https://github.com/gundestrup/jekyll-documents/actions/workflows/test.yml)
|
|
4
|
+
[](https://codecov.io/gh/gundestrup/jekyll-documents)
|
|
5
|
+
[](https://rubygems.org/gems/jekyll-documents)
|
|
6
|
+
[](https://rubygems.org/gems/jekyll-documents)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://deepwiki.com/gundestrup/jekyll-documents)
|
|
2
9
|
|
|
3
10
|
Turn files in `assets/documents/` into browsable document pages.
|
|
4
11
|
|
|
12
|
+
**Requirements**: Ruby 3.3+ • Jekyll 4.4+
|
|
13
|
+
|
|
5
14
|
**Features**: Auto-collection • File icons • Categories • Search
|
|
6
15
|
|
|
16
|
+
[](https://deepwiki.com/gundestrup/jekyll-documents)
|
|
17
|
+
|
|
7
18
|
## Quick Start
|
|
8
19
|
|
|
9
20
|
```ruby
|
|
@@ -52,10 +63,15 @@ documents:
|
|
|
52
63
|
icon_set: "color"
|
|
53
64
|
```
|
|
54
65
|
|
|
66
|
+
Render a document icon in templates with the context-aware tag:
|
|
67
|
+
|
|
55
68
|
```liquid
|
|
56
|
-
{
|
|
69
|
+
{% document_icon page %}
|
|
57
70
|
```
|
|
58
71
|
|
|
72
|
+
The tag applies the configured icon set and site `baseurl` automatically. The `file_type_icon` and
|
|
73
|
+
`file_type_icon_tag` filters remain available for lower-level or Ruby-side use.
|
|
74
|
+
|
|
59
75
|
## Usage
|
|
60
76
|
|
|
61
77
|
```liquid
|
|
@@ -78,15 +94,19 @@ See [configuration.rb](lib/jekyll/documents/configuration.rb) for all options.
|
|
|
78
94
|
## Development
|
|
79
95
|
|
|
80
96
|
```bash
|
|
81
|
-
rake #
|
|
82
|
-
rake
|
|
97
|
+
rake # Ruby quality checks
|
|
98
|
+
rake test # Rebuild/install gem + Ruby and browser tests
|
|
99
|
+
rake browser_test # Browser search tests (Node.js + Chromium required)
|
|
100
|
+
rake quick # Fast Ruby check
|
|
83
101
|
rake help # Show commands
|
|
84
102
|
```
|
|
85
103
|
|
|
86
|
-
**Quality**:
|
|
104
|
+
**Quality**: 100% coverage • 0 vulnerabilities • 0 offenses • 0 warnings
|
|
87
105
|
|
|
88
106
|
See [README.Development.md](README.Development.md) for details.
|
|
89
107
|
|
|
108
|
+
**DeepWiki**: [https://deepwiki.com/gundestrup/jekyll-documents](https://deepwiki.com/gundestrup/jekyll-documents)
|
|
109
|
+
|
|
90
110
|
## Release
|
|
91
111
|
|
|
92
112
|
```bash
|
|
@@ -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/
|
|
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
|
-
{
|
|
5
|
+
{% document_icon doc %}
|
|
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>
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
{% assign documents_config = site.data.jekyll_documents %}
|
|
2
|
+
{% assign index_path = documents_config.json_index_path | default: '/documents.json' %}
|
|
3
|
+
{% if documents_config.json_index != false %}
|
|
1
4
|
<div class="documents-search">
|
|
2
5
|
<input id="doc-search-input" type="search" placeholder="Search documents…" aria-label="Search documents" />
|
|
3
6
|
<div id="doc-search-results" class="documents-search-results"></div>
|
|
4
7
|
</div>
|
|
8
|
+
<script>
|
|
9
|
+
window.__jekyllDocumentsIndexPath = {{ index_path | relative_url | jsonify }};
|
|
10
|
+
window.__jekyllDocumentsBaseurl = {{ site.baseurl | jsonify }};
|
|
11
|
+
</script>
|
|
12
|
+
<script src="https://unpkg.com/lunr/lunr.js"></script>
|
|
13
|
+
<script src="{{ '/assets/js/documents-search.js' | relative_url }}"></script>
|
|
14
|
+
{% endif %}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{% assign count = include.count | default:
|
|
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
|
-
{
|
|
6
|
+
{% document_icon doc %}
|
|
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>
|
data/_layouts/document.html
CHANGED
|
@@ -1,80 +1,38 @@
|
|
|
1
|
-
/* Minimal client-side search using Lunr and
|
|
1
|
+
/* Minimal client-side search using Lunr and the generated document index.
|
|
2
2
|
Usage on any page:
|
|
3
3
|
{% include documents_search.html %}
|
|
4
|
-
<script src="https://unpkg.com/lunr/lunr.js"></script>
|
|
5
|
-
<script src="{{ '/assets/js/documents-search.js' | relative_url }}"></script>
|
|
6
4
|
*/
|
|
7
5
|
(function () {
|
|
8
6
|
const input = document.getElementById("doc-search-input");
|
|
9
7
|
const resultsEl = document.getElementById("doc-search-results");
|
|
10
8
|
if (!input || !resultsEl) return;
|
|
11
9
|
|
|
12
|
-
const indexUrl =
|
|
10
|
+
const indexUrl = window.__jekyllDocumentsIndexPath || "/documents.json";
|
|
13
11
|
let idx, docs = [];
|
|
14
12
|
|
|
15
|
-
function escapeHtml(
|
|
13
|
+
function escapeHtml(value) {
|
|
16
14
|
const div = document.createElement("div");
|
|
17
|
-
div.appendChild(document.createTextNode(
|
|
18
|
-
return div.innerHTML
|
|
15
|
+
div.appendChild(document.createTextNode(String(value)));
|
|
16
|
+
return div.innerHTML
|
|
17
|
+
.replace(/"/g, """)
|
|
18
|
+
.replace(/'/g, "'");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"ppt": "/assets/icons/color/ppt-document-svgrepo-com.svg",
|
|
30
|
-
"xlsx": "/assets/icons/color/excel-document-svgrepo-com.svg",
|
|
31
|
-
"xls": "/assets/icons/color/excel-document-svgrepo-com.svg",
|
|
32
|
-
"txt": "/assets/icons/color/txt-document-svgrepo-com.svg",
|
|
33
|
-
"zip": "/assets/icons/color/zip-document-svgrepo-com.svg"
|
|
34
|
-
},
|
|
35
|
-
"lines": {
|
|
36
|
-
"pdf": "/assets/icons/lines/pdf-svgrepo-com.svg",
|
|
37
|
-
"doc": "/assets/icons/lines/doc-svgrepo-com.svg",
|
|
38
|
-
"docx": "/assets/icons/lines/doc-svgrepo-com.svg",
|
|
39
|
-
"ppt": "/assets/icons/lines/ppt-svgrepo-com.svg",
|
|
40
|
-
"pptx": "/assets/icons/lines/ppt-svgrepo-com.svg",
|
|
41
|
-
"xls": "/assets/icons/lines/xls-svgrepo-com.svg",
|
|
42
|
-
"xlsx": "/assets/icons/lines/xls-svgrepo-com.svg",
|
|
43
|
-
"txt": "/assets/icons/lines/txt-svgrepo-com.svg",
|
|
44
|
-
"zip": "/assets/icons/lines/zip-svgrepo-com.svg"
|
|
45
|
-
},
|
|
46
|
-
"minimal": {
|
|
47
|
-
"pdf": "/assets/icons/minimal/pdf-svgrepo-com.svg",
|
|
48
|
-
"doc": "/assets/icons/minimal/doc-svgrepo-com.svg",
|
|
49
|
-
"docx": "/assets/icons/minimal/doc-svgrepo-com.svg",
|
|
50
|
-
"ppt": "/assets/icons/minimal/ppt-svgrepo-com.svg",
|
|
51
|
-
"pptx": "/assets/icons/minimal/ppt-svgrepo-com.svg",
|
|
52
|
-
"xls": "/assets/icons/minimal/xls-svgrepo-com.svg",
|
|
53
|
-
"xlsx": "/assets/icons/minimal/xls-svgrepo-com.svg",
|
|
54
|
-
"txt": "/assets/icons/minimal/txt-svgrepo-com.svg",
|
|
55
|
-
"zip": "/assets/icons/minimal/zip-svgrepo-com.svg"
|
|
56
|
-
},
|
|
57
|
-
"ultra-minimal": {
|
|
58
|
-
"pdf": "/assets/icons/ultra-minimal/pdf.svg",
|
|
59
|
-
"doc": "/assets/icons/ultra-minimal/doc.svg",
|
|
60
|
-
"docx": "/assets/icons/ultra-minimal/doc.svg",
|
|
61
|
-
"ppt": "/assets/icons/ultra-minimal/ppt.svg",
|
|
62
|
-
"pptx": "/assets/icons/ultra-minimal/ppt.svg",
|
|
63
|
-
"xls": "/assets/icons/ultra-minimal/xls.svg",
|
|
64
|
-
"xlsx": "/assets/icons/ultra-minimal/xls.svg",
|
|
65
|
-
"txt": "/assets/icons/ultra-minimal/txt.svg",
|
|
66
|
-
"zip": "/assets/icons/ultra-minimal/zip.svg"
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
const icons = iconMap[iconSet] || iconMap["color"];
|
|
70
|
-
return icons[fileType] || `/assets/icons/${iconSet}/unknown-document-svgrepo-com.svg`;
|
|
21
|
+
function withBaseurl(url) {
|
|
22
|
+
const value = String(url || "");
|
|
23
|
+
if (!value || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(value)) return value;
|
|
24
|
+
|
|
25
|
+
const baseurl = String(window.__jekyllDocumentsBaseurl || "")
|
|
26
|
+
.replace(/^\/+|\/+$/g, "");
|
|
27
|
+
const path = value.replace(/^\/+/, "");
|
|
28
|
+
return `/${[baseurl, path].filter(Boolean).join("/")}`;
|
|
71
29
|
}
|
|
72
30
|
|
|
73
|
-
function getIconTag(fileType) {
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
return `<img src="${
|
|
31
|
+
function getIconTag(fileType, iconUrl) {
|
|
32
|
+
if (!iconUrl) return "";
|
|
33
|
+
const altText = `${String(fileType || "file").toUpperCase()} file`;
|
|
34
|
+
const url = escapeHtml(withBaseurl(iconUrl));
|
|
35
|
+
return `<img src="${url}" alt="${escapeHtml(altText)}" class="file-icon" />`;
|
|
78
36
|
}
|
|
79
37
|
|
|
80
38
|
function render(matches) {
|
|
@@ -82,21 +40,27 @@
|
|
|
82
40
|
resultsEl.innerHTML = "<p>No results.</p>";
|
|
83
41
|
return;
|
|
84
42
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
43
|
+
|
|
44
|
+
const items = matches.slice(0, 20).map(match => {
|
|
45
|
+
const document = docs.find(item => item.url === match.ref);
|
|
46
|
+
if (!document) return "";
|
|
47
|
+
|
|
48
|
+
const title = escapeHtml(document.title || "Untitled");
|
|
49
|
+
const category = escapeHtml(document.category || "uncategorized");
|
|
50
|
+
const date = document.date ? `<small>${escapeHtml(document.date)}</small>` : "";
|
|
51
|
+
const icon = getIconTag(document.file_type, document.icon_url);
|
|
52
|
+
const url = escapeHtml(withBaseurl(document.url));
|
|
53
|
+
return `<li>${icon}<a href="${url}">${title}</a> ` +
|
|
54
|
+
`<small>[${category}]</small> ${date}</li>`;
|
|
93
55
|
});
|
|
94
56
|
resultsEl.innerHTML = `<ul class="documents-search-list">${items.join("")}</ul>`;
|
|
95
57
|
}
|
|
96
58
|
|
|
97
59
|
function debounce(fn, wait) {
|
|
98
|
-
let
|
|
99
|
-
|
|
60
|
+
let timer;
|
|
61
|
+
return function (...args) {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
timer = setTimeout(() => fn.apply(this, args), wait);
|
|
100
64
|
};
|
|
101
65
|
}
|
|
102
66
|
|
|
@@ -106,34 +70,48 @@
|
|
|
106
70
|
this.field("title", { boost: 10 });
|
|
107
71
|
this.field("category", { boost: 5 });
|
|
108
72
|
this.field("slug");
|
|
109
|
-
docs.forEach(
|
|
73
|
+
docs.forEach(document => this.add(document));
|
|
110
74
|
});
|
|
111
75
|
}
|
|
112
76
|
|
|
113
|
-
function doSearch(
|
|
114
|
-
if (!idx || !
|
|
77
|
+
function doSearch(query) {
|
|
78
|
+
if (!idx || !query || query.trim().length === 0) {
|
|
115
79
|
resultsEl.innerHTML = "";
|
|
116
80
|
return;
|
|
117
81
|
}
|
|
82
|
+
|
|
118
83
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
render(res);
|
|
84
|
+
render(idx.search(query));
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// If Lunr query syntax fails, fall back to a simple contains filter.
|
|
87
|
+
const normalizedQuery = query.toLowerCase();
|
|
88
|
+
const matches = docs.filter(document =>
|
|
89
|
+
[document.title, document.category, document.slug].some(value =>
|
|
90
|
+
String(value || "").toLowerCase().includes(normalizedQuery)
|
|
91
|
+
)
|
|
92
|
+
).map(document => ({ ref: document.url }));
|
|
93
|
+
render(matches);
|
|
130
94
|
}
|
|
131
95
|
}
|
|
132
96
|
|
|
133
97
|
fetch(indexUrl)
|
|
134
|
-
.then(
|
|
135
|
-
|
|
136
|
-
|
|
98
|
+
.then(response => {
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
throw new Error(`Search index request failed: ${response.status}`);
|
|
101
|
+
}
|
|
102
|
+
return response.json();
|
|
103
|
+
})
|
|
104
|
+
.then(json => {
|
|
105
|
+
if (!Array.isArray(json)) {
|
|
106
|
+
throw new Error("Search index must be a JSON array");
|
|
107
|
+
}
|
|
108
|
+
docs = json;
|
|
109
|
+
buildIndex();
|
|
110
|
+
})
|
|
111
|
+
.catch(error => {
|
|
112
|
+
resultsEl.innerHTML = "<p>Document search is currently unavailable.</p>";
|
|
113
|
+
console.error("Documents search index failed:", error);
|
|
114
|
+
});
|
|
137
115
|
|
|
138
|
-
input.addEventListener("input", debounce(
|
|
116
|
+
input.addEventListener("input", debounce(event => doSearch(event.target.value), 150));
|
|
139
117
|
})();
|
data/jekyll-documents.gemspec
CHANGED
|
@@ -25,20 +25,20 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
"rubygems_mfa_required" => "true"
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
spec.required_ruby_version = ">=
|
|
28
|
+
spec.required_ruby_version = ">= 3.3"
|
|
29
29
|
|
|
30
30
|
spec.files = Dir.glob("{lib,assets,_includes,_layouts}/**/*") +
|
|
31
31
|
["README.md", "CHANGELOG.md", "LICENSE", "jekyll-documents.gemspec"]
|
|
32
32
|
spec.require_paths = ["lib"]
|
|
33
33
|
|
|
34
|
-
spec.add_dependency "jekyll", ">= 4.0"
|
|
34
|
+
spec.add_dependency "jekyll", ">= 4.4", "< 5.0"
|
|
35
35
|
|
|
36
36
|
spec.add_development_dependency "bundler-audit", "~> 0.9"
|
|
37
37
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
38
|
-
spec.add_development_dependency "reek", "~> 6.
|
|
39
|
-
spec.add_development_dependency "rspec", "~> 3.
|
|
40
|
-
spec.add_development_dependency "rubocop", "~> 1.
|
|
41
|
-
spec.add_development_dependency "rubocop-performance", "~> 1.
|
|
42
|
-
spec.add_development_dependency "simplecov", "~> 0
|
|
38
|
+
spec.add_development_dependency "reek", "~> 6.5"
|
|
39
|
+
spec.add_development_dependency "rspec", "~> 3.13"
|
|
40
|
+
spec.add_development_dependency "rubocop", "~> 1.88"
|
|
41
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.26"
|
|
42
|
+
spec.add_development_dependency "simplecov", "~> 1.0"
|
|
43
43
|
spec.add_development_dependency "yard", "~> 0.9"
|
|
44
44
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Documents
|
|
5
|
+
# Registers gem-packaged frontend assets for copying into the generated site.
|
|
6
|
+
class AssetsGenerator < ::Jekyll::Generator
|
|
7
|
+
safe true
|
|
8
|
+
priority :normal
|
|
9
|
+
|
|
10
|
+
def generate(site)
|
|
11
|
+
package_root = File.expand_path("../../..", __dir__)
|
|
12
|
+
site.data["jekyll_documents"] = configuration(site)
|
|
13
|
+
register_assets(site, package_root)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def configuration(site)
|
|
19
|
+
config = Configuration.read(site)
|
|
20
|
+
{
|
|
21
|
+
"json_index" => config["json_index"],
|
|
22
|
+
"json_index_path" => config["json_index_path"]
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def register_assets(site, package_root)
|
|
27
|
+
assets_root = File.join(package_root, "assets")
|
|
28
|
+
Dir.glob(File.join(assets_root, "**", "*")).each do |path|
|
|
29
|
+
register_asset(site, package_root, path)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def register_asset(site, package_root, path)
|
|
34
|
+
return unless File.file?(path)
|
|
35
|
+
|
|
36
|
+
relative_path = path.delete_prefix("#{package_root}/")
|
|
37
|
+
return if site.static_files.any? { |file| file.relative_path == relative_path }
|
|
38
|
+
|
|
39
|
+
site.static_files << ::Jekyll::StaticFile.new(
|
|
40
|
+
site,
|
|
41
|
+
package_root,
|
|
42
|
+
File.dirname(relative_path),
|
|
43
|
+
File.basename(relative_path)
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -13,6 +13,9 @@ module Jekyll
|
|
|
13
13
|
# Lines: https://www.svgrepo.com/collection/simple-file-types-line-vectors/
|
|
14
14
|
# Minimal: https://www.svgrepo.com/collection/file-type-minimal-icons/
|
|
15
15
|
# Ultra-minimal: Based on simple document icons
|
|
16
|
+
LINES_ICON_PATH = "/assets/icons/lines/"
|
|
17
|
+
MINIMAL_ICON_PATH = "/assets/icons/minimal/"
|
|
18
|
+
|
|
16
19
|
ICON_MAP = {
|
|
17
20
|
"color" => {
|
|
18
21
|
"pdf" => "/assets/icons/color/pdf-document-svgrepo-com.svg",
|
|
@@ -38,42 +41,59 @@ module Jekyll
|
|
|
38
41
|
"rtf" => "/assets/icons/color/rtf-document-svgrepo-com.svg"
|
|
39
42
|
},
|
|
40
43
|
"lines" => {
|
|
41
|
-
"pdf" => "
|
|
42
|
-
"docx" => "
|
|
43
|
-
"doc" => "
|
|
44
|
-
"pptx" => "
|
|
45
|
-
"ppt" => "
|
|
46
|
-
"xlsx" => "
|
|
47
|
-
"xls" => "
|
|
48
|
-
"odt" => "
|
|
49
|
-
"ods" => "
|
|
50
|
-
"odp" => "
|
|
51
|
-
"txt" => "
|
|
52
|
-
"zip" => "
|
|
53
|
-
"mp3" => "
|
|
54
|
-
"mp4" => "
|
|
55
|
-
"jpg" => "
|
|
56
|
-
"jpeg" => "
|
|
57
|
-
"png" => "
|
|
44
|
+
"pdf" => "#{LINES_ICON_PATH}pdf-file-type-svgrepo-com.svg",
|
|
45
|
+
"docx" => "#{LINES_ICON_PATH}word-file-type-svgrepo-com.svg",
|
|
46
|
+
"doc" => "#{LINES_ICON_PATH}word-file-type-svgrepo-com.svg",
|
|
47
|
+
"pptx" => "#{LINES_ICON_PATH}ppt-file-type-svgrepo-com.svg",
|
|
48
|
+
"ppt" => "#{LINES_ICON_PATH}ppt-file-type-svgrepo-com.svg",
|
|
49
|
+
"xlsx" => "#{LINES_ICON_PATH}excel-file-type-svgrepo-com.svg",
|
|
50
|
+
"xls" => "#{LINES_ICON_PATH}excel-file-type-svgrepo-com.svg",
|
|
51
|
+
"odt" => "#{LINES_ICON_PATH}word-file-type-svgrepo-com.svg",
|
|
52
|
+
"ods" => "#{LINES_ICON_PATH}excel-file-type-svgrepo-com.svg",
|
|
53
|
+
"odp" => "#{LINES_ICON_PATH}ppt-file-type-svgrepo-com.svg",
|
|
54
|
+
"txt" => "#{LINES_ICON_PATH}txt-file-type-svgrepo-com.svg",
|
|
55
|
+
"zip" => "#{LINES_ICON_PATH}zip-file-type-svgrepo-com.svg",
|
|
56
|
+
"mp3" => "#{LINES_ICON_PATH}mp3-file-type-svgrepo-com.svg",
|
|
57
|
+
"mp4" => "#{LINES_ICON_PATH}other-file-type-svgrepo-com.svg",
|
|
58
|
+
"jpg" => "#{LINES_ICON_PATH}jpg-file-type-svgrepo-com.svg",
|
|
59
|
+
"jpeg" => "#{LINES_ICON_PATH}jpg-file-type-svgrepo-com.svg",
|
|
60
|
+
"png" => "#{LINES_ICON_PATH}png-file-type-svgrepo-com.svg"
|
|
58
61
|
},
|
|
59
62
|
"minimal" => {
|
|
60
|
-
"pdf" => "
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
"
|
|
63
|
+
"pdf" => "#{MINIMAL_ICON_PATH}" \
|
|
64
|
+
"extension-file-format-pdf-document-file-format-svgrepo-com.svg",
|
|
65
|
+
"docx" => "#{MINIMAL_ICON_PATH}" \
|
|
66
|
+
"extension-file-format-document-file-format-svgrepo-com.svg",
|
|
67
|
+
"doc" => "#{MINIMAL_ICON_PATH}" \
|
|
68
|
+
"extension-file-format-document-file-format-svgrepo-com.svg",
|
|
69
|
+
"pptx" => "#{MINIMAL_ICON_PATH}" \
|
|
70
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
71
|
+
"ppt" => "#{MINIMAL_ICON_PATH}" \
|
|
72
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
73
|
+
"xlsx" => "#{MINIMAL_ICON_PATH}" \
|
|
74
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
75
|
+
"xls" => "#{MINIMAL_ICON_PATH}" \
|
|
76
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
77
|
+
"odt" => "#{MINIMAL_ICON_PATH}" \
|
|
78
|
+
"extension-file-format-document-file-format-svgrepo-com.svg",
|
|
79
|
+
"ods" => "#{MINIMAL_ICON_PATH}" \
|
|
80
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
81
|
+
"odp" => "#{MINIMAL_ICON_PATH}" \
|
|
82
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
83
|
+
"txt" => "#{MINIMAL_ICON_PATH}" \
|
|
84
|
+
"extension-file-format-txt-document-file-format-svgrepo-com.svg",
|
|
85
|
+
"zip" => "#{MINIMAL_ICON_PATH}" \
|
|
86
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
87
|
+
"mp3" => "#{MINIMAL_ICON_PATH}" \
|
|
88
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
89
|
+
"mp4" => "#{MINIMAL_ICON_PATH}" \
|
|
90
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
91
|
+
"jpg" => "#{MINIMAL_ICON_PATH}" \
|
|
92
|
+
"extension-file-format-jpg-document-file-format-svgrepo-com.svg",
|
|
93
|
+
"jpeg" => "#{MINIMAL_ICON_PATH}" \
|
|
94
|
+
"file-format-jpeg-document-extension-file-format-svgrepo-com.svg",
|
|
95
|
+
"png" => "#{MINIMAL_ICON_PATH}" \
|
|
96
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg"
|
|
77
97
|
},
|
|
78
98
|
"ultra-minimal" => {
|
|
79
99
|
"pdf" => "/assets/icons/ultra-minimal/pdf.svg",
|
|
@@ -95,6 +115,27 @@ module Jekyll
|
|
|
95
115
|
}
|
|
96
116
|
}.freeze
|
|
97
117
|
|
|
118
|
+
UNKNOWN_ICON_MAP = {
|
|
119
|
+
"color" => "/assets/icons/color/unknown-document-svgrepo-com.svg",
|
|
120
|
+
"lines" => "#{LINES_ICON_PATH}other-file-type-svgrepo-com.svg",
|
|
121
|
+
"minimal" => "#{MINIMAL_ICON_PATH}" \
|
|
122
|
+
"extension-file-format-document-file-format-2-svgrepo-com.svg",
|
|
123
|
+
"ultra-minimal" => "/assets/icons/ultra-minimal/file.svg"
|
|
124
|
+
}.freeze
|
|
125
|
+
|
|
126
|
+
# Returns the icon URL for a given file type and explicit icon set
|
|
127
|
+
# @param file_type [String] the file extension (e.g., 'pdf', 'docx')
|
|
128
|
+
# @param icon_set [String] the icon set name (defaults to 'color')
|
|
129
|
+
# @return [String] the icon file URL
|
|
130
|
+
# @example
|
|
131
|
+
# FileTypeIcons.icon_for('pdf', 'lines') #=>
|
|
132
|
+
# "/assets/icons/lines/pdf-file-type-svgrepo-com.svg"
|
|
133
|
+
def self.icon_for(file_type, icon_set = "color")
|
|
134
|
+
selected_set = ICON_MAP.key?(icon_set) ? icon_set : "color"
|
|
135
|
+
icons = ICON_MAP[selected_set]
|
|
136
|
+
icons[file_type.to_s.downcase] || UNKNOWN_ICON_MAP[selected_set]
|
|
137
|
+
end
|
|
138
|
+
|
|
98
139
|
# Returns the icon URL for a given file type using configured icon set
|
|
99
140
|
# @param file_type [String] the file extension (e.g., 'pdf', 'docx')
|
|
100
141
|
# @param context [Liquid::Context] the Liquid context for accessing site config
|
|
@@ -102,10 +143,7 @@ module Jekyll
|
|
|
102
143
|
# @example
|
|
103
144
|
# file_type_icon('pdf') #=> "/assets/icons/color/pdf.svg"
|
|
104
145
|
def file_type_icon(file_type, context = nil)
|
|
105
|
-
|
|
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"
|
|
146
|
+
Jekyll::Documents::FileTypeIcons.icon_for(file_type, get_icon_set(context))
|
|
109
147
|
end
|
|
110
148
|
|
|
111
149
|
# Returns an HTML img tag for the file type icon
|
|
@@ -119,7 +157,7 @@ module Jekyll
|
|
|
119
157
|
# '<img src="/assets/icons/color/pdf.svg" alt="PDF file" class="file-icon" />'
|
|
120
158
|
def file_type_icon_tag(file_type, css_class: "file-icon", alt: nil, context: nil)
|
|
121
159
|
url = file_type_icon(file_type, context)
|
|
122
|
-
alt_text = alt || "#{file_type.upcase} file"
|
|
160
|
+
alt_text = alt || "#{file_type.to_s.upcase} file"
|
|
123
161
|
%(<img src="#{url}" alt="#{alt_text}" class="#{css_class}" />)
|
|
124
162
|
end
|
|
125
163
|
|
|
@@ -131,11 +169,8 @@ module Jekyll
|
|
|
131
169
|
def get_icon_set(context)
|
|
132
170
|
return "color" unless context
|
|
133
171
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
icon_set = documents_config["icon_set"] if documents_config
|
|
137
|
-
|
|
138
|
-
# Validate icon set exists
|
|
172
|
+
site = context.registers[:site]
|
|
173
|
+
icon_set = site&.config&.dig("documents", "icon_set")
|
|
139
174
|
return "color" unless icon_set && ICON_MAP.key?(icon_set)
|
|
140
175
|
|
|
141
176
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
data = doc.data
|
|
59
|
+
data["layout"] = @config["layout"]
|
|
60
|
+
data["title"] = title
|
|
61
|
+
data["date"] = date ? date.to_time : 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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
142
|
+
slug = basename.sub(/^\d{4}-\d{2}-\d{2}_/, "")
|
|
136
143
|
if @config["slug_danish_map"]
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
slug = slug.gsub(/[æøåÆØÅ]/,
|
|
145
|
+
{ "æ" => "ae", "ø" => "oe", "å" => "aa", "Æ" => "Ae", "Ø" => "Oe",
|
|
146
|
+
"Å" => "Aa" })
|
|
140
147
|
end
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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,17 @@ module Jekyll
|
|
|
16
16
|
docs = site.collections["documents"]&.docs || []
|
|
17
17
|
return if docs.empty?
|
|
18
18
|
|
|
19
|
-
items = docs.map do |
|
|
19
|
+
items = docs.map do |doc|
|
|
20
|
+
data = doc.data
|
|
20
21
|
{
|
|
21
|
-
"url" =>
|
|
22
|
-
"title" =>
|
|
23
|
-
"category" =>
|
|
24
|
-
"date" => (
|
|
25
|
-
"slug" =>
|
|
26
|
-
"file_type" =>
|
|
27
|
-
"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"],
|
|
29
|
+
"icon_url" => data["icon_url"]
|
|
28
30
|
}
|
|
29
31
|
end
|
|
30
32
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
module Documents
|
|
7
|
+
# Registers gem-packaged _layouts and _includes with the site so Jekyll can
|
|
8
|
+
# discover them without the gem being declared as a theme.
|
|
9
|
+
#
|
|
10
|
+
# Jekyll only auto-discovers _layouts and _includes from theme gems (set via
|
|
11
|
+
# the +theme:+ config key). Plugin gems loaded via +group :jekyll_plugins+
|
|
12
|
+
# do not get their template directories added to Jekyll's lookup paths.
|
|
13
|
+
#
|
|
14
|
+
# This hook copies any gem-packaged layouts and includes into the site's
|
|
15
|
+
# source directories when the user has not provided their own. Files already
|
|
16
|
+
# present in the site take precedence (user overrides are never clobbered).
|
|
17
|
+
module LayoutRegistrar
|
|
18
|
+
GEM_ROOT = File.expand_path("../../..", __dir__)
|
|
19
|
+
TEMPLATE_DIRS = %w[_layouts _includes].freeze
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# Copies gem-packaged template files into the site source.
|
|
23
|
+
# @param site [Jekyll::Site] the Jekyll site instance
|
|
24
|
+
# @return [void]
|
|
25
|
+
def register(site)
|
|
26
|
+
TEMPLATE_DIRS.each do |dir|
|
|
27
|
+
gem_dir = File.join(GEM_ROOT, dir)
|
|
28
|
+
next unless Dir.exist?(gem_dir)
|
|
29
|
+
|
|
30
|
+
copy_templates(gem_dir, File.join(site.source, dir))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Recursively copies files from +source_dir+ to +dest_dir+, skipping any
|
|
37
|
+
# file that already exists in the destination (user overrides win).
|
|
38
|
+
# @param source_dir [String] gem template directory
|
|
39
|
+
# @param dest_dir [String] site template directory
|
|
40
|
+
# @return [void]
|
|
41
|
+
def copy_templates(source_dir, dest_dir)
|
|
42
|
+
Dir.glob(File.join(source_dir, "**", "*")).each do |path|
|
|
43
|
+
next unless File.file?(path)
|
|
44
|
+
|
|
45
|
+
relative = path.delete_prefix("#{source_dir}/")
|
|
46
|
+
destination = File.join(dest_dir, relative)
|
|
47
|
+
next if File.exist?(destination)
|
|
48
|
+
|
|
49
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
50
|
+
FileUtils.cp(path, destination)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Jekyll::Hooks.register :site, :after_init do |site|
|
|
59
|
+
Jekyll::Documents::LayoutRegistrar.register(site)
|
|
60
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Documents
|
|
5
|
+
class DocumentIconTag < Liquid::Tag
|
|
6
|
+
public_class_method :new
|
|
7
|
+
|
|
8
|
+
include Jekyll::Filters::URLFilters
|
|
9
|
+
|
|
10
|
+
def initialize(tag_name, markup, tokens)
|
|
11
|
+
super
|
|
12
|
+
@document_expression, @options = parse_markup(markup)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def render(context)
|
|
16
|
+
document = context.find_variable(@document_expression)
|
|
17
|
+
return "" unless document
|
|
18
|
+
|
|
19
|
+
@context = context
|
|
20
|
+
icon_url = document_value(document, "icon_url") || fallback_icon(document, context)
|
|
21
|
+
url = relative_url(icon_url)
|
|
22
|
+
file_type = document_value(document, "file_type").to_s
|
|
23
|
+
alt = @options["alt"] || "#{file_type.upcase} file"
|
|
24
|
+
css_class = @options["class"] || "file-icon"
|
|
25
|
+
|
|
26
|
+
%(<img src="#{escape_html(url)}" alt="#{escape_html(alt)}" ) \
|
|
27
|
+
+ %(class="#{escape_html(css_class)}" />)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def document_value(document, key)
|
|
33
|
+
return document.data[key] if document.respond_to?(:data)
|
|
34
|
+
return document[key] if document.respond_to?(:[])
|
|
35
|
+
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fallback_icon(document, context)
|
|
40
|
+
file_type = document_value(document, "file_type")
|
|
41
|
+
icon_set = Configuration.read(context.registers[:site])["icon_set"]
|
|
42
|
+
FileTypeIcons.icon_for(file_type, icon_set)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse_markup(markup)
|
|
46
|
+
expression = markup.to_s.strip[/\A[^\s]+/]
|
|
47
|
+
options = {}
|
|
48
|
+
markup.to_s.scan(
|
|
49
|
+
/(\w+)\s*:\s*(?:'([^']*)'|"([^"]*)"|([^\s]+))/
|
|
50
|
+
) do |key, single, double, bare|
|
|
51
|
+
options[key] = single || double || bare
|
|
52
|
+
end
|
|
53
|
+
[expression, options]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def escape_html(value)
|
|
57
|
+
value.to_s.gsub(/[&<>"']/,
|
|
58
|
+
"&" => "&",
|
|
59
|
+
"<" => "<",
|
|
60
|
+
">" => ">",
|
|
61
|
+
'"' => """,
|
|
62
|
+
"'" => "'")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
Liquid::Template.register_tag("document_icon", Jekyll::Documents::DocumentIconTag)
|
|
@@ -17,14 +17,15 @@ module Jekyll
|
|
|
17
17
|
category = @args["category"]
|
|
18
18
|
|
|
19
19
|
docs = site.collections["documents"]&.docs || []
|
|
20
|
-
docs = docs.select { |
|
|
21
|
-
docs = docs.sort_by { |
|
|
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 |
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 |
|
|
54
|
-
if
|
|
55
|
-
args[
|
|
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[
|
|
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
|
|
data/lib/jekyll-documents.rb
CHANGED
|
@@ -7,10 +7,13 @@ require_relative "jekyll/documents/utils"
|
|
|
7
7
|
require_relative "jekyll/documents/filters"
|
|
8
8
|
require_relative "jekyll/documents/file_type_icons"
|
|
9
9
|
require_relative "jekyll/documents/generator"
|
|
10
|
+
require_relative "jekyll/documents/assets_generator"
|
|
10
11
|
require_relative "jekyll/documents/json_index_generator"
|
|
12
|
+
require_relative "jekyll/documents/layout_registrar"
|
|
11
13
|
|
|
12
14
|
# Liquid tag(s)
|
|
13
15
|
require_relative "jekyll/documents/tags/latest_documents"
|
|
16
|
+
require_relative "jekyll/documents/tags/document_icon"
|
|
14
17
|
|
|
15
18
|
module Jekyll
|
|
16
19
|
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.2
|
|
4
|
+
version: 0.3.2
|
|
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-
|
|
11
|
+
date: 2026-08-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -16,14 +16,20 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '4.
|
|
19
|
+
version: '4.4'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '5.0'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
27
|
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '4.
|
|
29
|
+
version: '4.4'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.0'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: bundler-audit
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,70 +64,70 @@ dependencies:
|
|
|
58
64
|
requirements:
|
|
59
65
|
- - "~>"
|
|
60
66
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '6.
|
|
67
|
+
version: '6.5'
|
|
62
68
|
type: :development
|
|
63
69
|
prerelease: false
|
|
64
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
71
|
requirements:
|
|
66
72
|
- - "~>"
|
|
67
73
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '6.
|
|
74
|
+
version: '6.5'
|
|
69
75
|
- !ruby/object:Gem::Dependency
|
|
70
76
|
name: rspec
|
|
71
77
|
requirement: !ruby/object:Gem::Requirement
|
|
72
78
|
requirements:
|
|
73
79
|
- - "~>"
|
|
74
80
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '3.
|
|
81
|
+
version: '3.13'
|
|
76
82
|
type: :development
|
|
77
83
|
prerelease: false
|
|
78
84
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
85
|
requirements:
|
|
80
86
|
- - "~>"
|
|
81
87
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '3.
|
|
88
|
+
version: '3.13'
|
|
83
89
|
- !ruby/object:Gem::Dependency
|
|
84
90
|
name: rubocop
|
|
85
91
|
requirement: !ruby/object:Gem::Requirement
|
|
86
92
|
requirements:
|
|
87
93
|
- - "~>"
|
|
88
94
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '1.
|
|
95
|
+
version: '1.88'
|
|
90
96
|
type: :development
|
|
91
97
|
prerelease: false
|
|
92
98
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
99
|
requirements:
|
|
94
100
|
- - "~>"
|
|
95
101
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '1.
|
|
102
|
+
version: '1.88'
|
|
97
103
|
- !ruby/object:Gem::Dependency
|
|
98
104
|
name: rubocop-performance
|
|
99
105
|
requirement: !ruby/object:Gem::Requirement
|
|
100
106
|
requirements:
|
|
101
107
|
- - "~>"
|
|
102
108
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '1.
|
|
109
|
+
version: '1.26'
|
|
104
110
|
type: :development
|
|
105
111
|
prerelease: false
|
|
106
112
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
113
|
requirements:
|
|
108
114
|
- - "~>"
|
|
109
115
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '1.
|
|
116
|
+
version: '1.26'
|
|
111
117
|
- !ruby/object:Gem::Dependency
|
|
112
118
|
name: simplecov
|
|
113
119
|
requirement: !ruby/object:Gem::Requirement
|
|
114
120
|
requirements:
|
|
115
121
|
- - "~>"
|
|
116
122
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '0
|
|
123
|
+
version: '1.0'
|
|
118
124
|
type: :development
|
|
119
125
|
prerelease: false
|
|
120
126
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
127
|
requirements:
|
|
122
128
|
- - "~>"
|
|
123
129
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '0
|
|
130
|
+
version: '1.0'
|
|
125
131
|
- !ruby/object:Gem::Dependency
|
|
126
132
|
name: yard
|
|
127
133
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -253,11 +259,14 @@ files:
|
|
|
253
259
|
- assets/js/documents-search.js
|
|
254
260
|
- jekyll-documents.gemspec
|
|
255
261
|
- lib/jekyll-documents.rb
|
|
262
|
+
- lib/jekyll/documents/assets_generator.rb
|
|
256
263
|
- lib/jekyll/documents/configuration.rb
|
|
257
264
|
- lib/jekyll/documents/file_type_icons.rb
|
|
258
265
|
- lib/jekyll/documents/filters.rb
|
|
259
266
|
- lib/jekyll/documents/generator.rb
|
|
260
267
|
- lib/jekyll/documents/json_index_generator.rb
|
|
268
|
+
- lib/jekyll/documents/layout_registrar.rb
|
|
269
|
+
- lib/jekyll/documents/tags/document_icon.rb
|
|
261
270
|
- lib/jekyll/documents/tags/latest_documents.rb
|
|
262
271
|
- lib/jekyll/documents/utils.rb
|
|
263
272
|
- lib/jekyll/documents/version.rb
|
|
@@ -279,14 +288,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
279
288
|
requirements:
|
|
280
289
|
- - ">="
|
|
281
290
|
- !ruby/object:Gem::Version
|
|
282
|
-
version: '
|
|
291
|
+
version: '3.3'
|
|
283
292
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
284
293
|
requirements:
|
|
285
294
|
- - ">="
|
|
286
295
|
- !ruby/object:Gem::Version
|
|
287
296
|
version: '0'
|
|
288
297
|
requirements: []
|
|
289
|
-
rubygems_version: 3.
|
|
298
|
+
rubygems_version: 3.5.22
|
|
290
299
|
signing_key:
|
|
291
300
|
specification_version: 4
|
|
292
301
|
summary: Auto-generate Jekyll pages for documents (PDF/DOCX/...) with category/date/title
|