jekyll-uj-powertools 1.6.23 → 1.7.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 +4 -4
- data/README.md +84 -0
- data/jekyll-uj-powertools.gemspec +1 -1
- data/lib/generators/dynamic-pages.rb +152 -0
- data/lib/generators/inject-properties.rb +1 -1
- data/lib/generators/limit-collections.rb +56 -0
- data/lib/jekyll-uj-powertools.rb +2 -0
- data/lib/tags/logo.rb +22 -17
- 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: 60803db8fb2a1e2c50aea11a0f49e964fc16d3323943ce0330f4312fd6468e37
|
|
4
|
+
data.tar.gz: 05f5ac27b7651b0d5592395cc510ba5b31e77f9b5ead9f35f0de6420d5919fd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17bb830ad04f8be16741f99f9dcaab5f723b7c9bb58f1ffbdfa0bf9a9637c0357e829c82dd4dc13feee2e70773bde48cd71e1cface15873f7232a60ea6880f4c
|
|
7
|
+
data.tar.gz: 21c374abbaa0e43c40bf1ecda75ac52eda70dd33140c69c972bf352eca2d571c35c5d70485947275a639c46b7df8e235f496a2b678a29008722ebeadb9e4e62b
|
data/README.md
CHANGED
|
@@ -294,6 +294,90 @@ Creates language-specific URLs for multilingual sites.
|
|
|
294
294
|
{% uj_translation_url target_lang, "/pricing" %}
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
+
## Generators
|
|
298
|
+
|
|
299
|
+
### Dynamic Pages Generator
|
|
300
|
+
Automatically generate pages from collection data based on frontmatter fields. This is useful for creating category, tag, or any taxonomy pages dynamically without manually creating each page.
|
|
301
|
+
|
|
302
|
+
#### Configuration
|
|
303
|
+
Add to your `_config.yml`:
|
|
304
|
+
|
|
305
|
+
```yaml
|
|
306
|
+
generators:
|
|
307
|
+
collection_categories:
|
|
308
|
+
# Example 1: Extract category from nested frontmatter field
|
|
309
|
+
- collection: recipes
|
|
310
|
+
field: recipe.cuisine # Dot notation for nested fields
|
|
311
|
+
layout: recipe-category
|
|
312
|
+
permalink: /recipes/:slug
|
|
313
|
+
title: ":name Recipes"
|
|
314
|
+
description: "Browse our collection of :name recipes."
|
|
315
|
+
|
|
316
|
+
# Example 2: Simple frontmatter field
|
|
317
|
+
- collection: products
|
|
318
|
+
field: category
|
|
319
|
+
layout: product-category
|
|
320
|
+
permalink: /products/:slug
|
|
321
|
+
title: ":name Products"
|
|
322
|
+
description: "Shop our :name products."
|
|
323
|
+
|
|
324
|
+
# Example 3: Minimal config (uses defaults)
|
|
325
|
+
- collection: articles
|
|
326
|
+
field: category
|
|
327
|
+
layout: article-category
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### Template Variables
|
|
331
|
+
Use these placeholders in `title`, `description`, and `permalink`:
|
|
332
|
+
- `:name` - The formatted category name (e.g., "Asian Cuisine")
|
|
333
|
+
- `:slug` - The URL-safe slug (e.g., "asian-cuisine")
|
|
334
|
+
|
|
335
|
+
#### Defaults
|
|
336
|
+
- `permalink`: `/:collection/:slug` (e.g., `/recipes/asian`)
|
|
337
|
+
- `title`: `:name`
|
|
338
|
+
- `description`: `Browse our collection of :name.`
|
|
339
|
+
|
|
340
|
+
#### Generated Page Data
|
|
341
|
+
Each generated page includes the following data accessible in the layout:
|
|
342
|
+
- `page.title` - The formatted title
|
|
343
|
+
- `page.description` - The formatted description
|
|
344
|
+
- `page.category_slug` - URL-safe slug (e.g., "asian-cuisine")
|
|
345
|
+
- `page.category_name` - Human-readable name (e.g., "Asian Cuisine")
|
|
346
|
+
- `page.collection_name` - Source collection name (e.g., "recipes")
|
|
347
|
+
- `page.meta.title` - SEO title with site name appended
|
|
348
|
+
- `page.meta.description` - SEO description
|
|
349
|
+
|
|
350
|
+
#### Example Layout
|
|
351
|
+
Create a layout file (e.g., `_layouts/recipe-category.html`) to display the category page:
|
|
352
|
+
|
|
353
|
+
```liquid
|
|
354
|
+
---
|
|
355
|
+
layout: default
|
|
356
|
+
---
|
|
357
|
+
<h1>{{ page.category_name }}</h1>
|
|
358
|
+
<p>{{ page.description }}</p>
|
|
359
|
+
|
|
360
|
+
{% assign items = site.recipes | where_exp: "item", "item.recipe.cuisine == page.category_name" %}
|
|
361
|
+
{% for item in items %}
|
|
362
|
+
<article>
|
|
363
|
+
<h2><a href="{{ item.url }}">{{ item.title }}</a></h2>
|
|
364
|
+
</article>
|
|
365
|
+
{% endfor %}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Development Config (`_config.dev.yml`)
|
|
369
|
+
Speed up dev builds by limiting collections. Create `_config.dev.yml` in your Jekyll source:
|
|
370
|
+
|
|
371
|
+
```yaml
|
|
372
|
+
limit_collections:
|
|
373
|
+
recipes: 50
|
|
374
|
+
products: 20
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Run with: `bundle exec jekyll serve --config _config.yml,_config.dev.yml`
|
|
378
|
+
|
|
379
|
+
UJ auto-loads this file in dev mode.
|
|
380
|
+
|
|
297
381
|
## Final notes
|
|
298
382
|
These examples show how you can use the features of `jekyll-uj-powertools` in your Jekyll site.
|
|
299
383
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Dynamic Pages Generator
|
|
2
|
+
# Automatically generates pages from collection data based on config
|
|
3
|
+
#
|
|
4
|
+
# Usage in _config.yml:
|
|
5
|
+
#
|
|
6
|
+
# generators:
|
|
7
|
+
# collection_categories:
|
|
8
|
+
# # Example 1: Extract category from frontmatter field
|
|
9
|
+
# - collection: recipes
|
|
10
|
+
# field: recipe.cuisine # Dot notation for nested fields
|
|
11
|
+
# layout: recipe-category
|
|
12
|
+
# permalink: /recipes/:slug
|
|
13
|
+
# title: ":name Recipes"
|
|
14
|
+
# description: "Browse our collection of :name recipes."
|
|
15
|
+
#
|
|
16
|
+
# # Example 2: Simple frontmatter field
|
|
17
|
+
# - collection: products
|
|
18
|
+
# field: category
|
|
19
|
+
# layout: product-category
|
|
20
|
+
# permalink: /products/:slug
|
|
21
|
+
# title: ":name Products"
|
|
22
|
+
# description: "Shop our :name products."
|
|
23
|
+
#
|
|
24
|
+
# # Example 3: Minimal config (uses defaults)
|
|
25
|
+
# - collection: articles
|
|
26
|
+
# field: category
|
|
27
|
+
# layout: article-category
|
|
28
|
+
#
|
|
29
|
+
# Template variables for title/description/permalink:
|
|
30
|
+
# :name - The formatted category name (e.g., "Asian Cuisine")
|
|
31
|
+
# :slug - The URL-safe slug (e.g., "asian-cuisine")
|
|
32
|
+
#
|
|
33
|
+
# Defaults:
|
|
34
|
+
# permalink: /:collection/:slug (e.g., /recipes/asian)
|
|
35
|
+
# title: :name
|
|
36
|
+
# description: "Browse our collection of :name."
|
|
37
|
+
|
|
38
|
+
module Jekyll
|
|
39
|
+
class DynamicPagesGenerator < Generator
|
|
40
|
+
safe true
|
|
41
|
+
priority :normal # Run before InjectProperties (:low) so dynamic pages get resolved data
|
|
42
|
+
|
|
43
|
+
def generate(site)
|
|
44
|
+
config = site.config.dig('generators', 'collection_categories')
|
|
45
|
+
return unless config.is_a?(Array)
|
|
46
|
+
|
|
47
|
+
config.each do |category_config|
|
|
48
|
+
generate_category_pages(site, category_config)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def generate_category_pages(site, config)
|
|
55
|
+
collection_name = config['collection']
|
|
56
|
+
field = config['field']
|
|
57
|
+
return unless collection_name && field
|
|
58
|
+
|
|
59
|
+
collection = site.collections[collection_name]
|
|
60
|
+
return unless collection
|
|
61
|
+
|
|
62
|
+
# Configuration with defaults
|
|
63
|
+
layout = config['layout']
|
|
64
|
+
permalink_template = config['permalink'] || "/#{collection_name}/:slug"
|
|
65
|
+
title_template = config['title'] || ':name'
|
|
66
|
+
description_template = config['description'] || 'Browse our collection of :name.'
|
|
67
|
+
|
|
68
|
+
# Extract unique categories from documents
|
|
69
|
+
categories = {}
|
|
70
|
+
|
|
71
|
+
collection.docs.each do |doc|
|
|
72
|
+
category_value = dig_value(doc.data, field)
|
|
73
|
+
next unless category_value && !category_value.empty?
|
|
74
|
+
|
|
75
|
+
category_slug = slugify(category_value)
|
|
76
|
+
next if categories.key?(category_slug)
|
|
77
|
+
|
|
78
|
+
# Format category name: titleize
|
|
79
|
+
category_name = titleize(category_value)
|
|
80
|
+
|
|
81
|
+
# Apply templates
|
|
82
|
+
permalink = apply_template(permalink_template, category_name, category_slug)
|
|
83
|
+
title = apply_template(title_template, category_name, category_slug)
|
|
84
|
+
description = apply_template(description_template, category_name, category_slug)
|
|
85
|
+
|
|
86
|
+
categories[category_slug] = {
|
|
87
|
+
'slug' => category_slug,
|
|
88
|
+
'name' => category_name,
|
|
89
|
+
'title' => title,
|
|
90
|
+
'description' => description,
|
|
91
|
+
'permalink' => permalink
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Generate a page for each category
|
|
96
|
+
categories.each do |slug, data|
|
|
97
|
+
page = DynamicCategoryPage.new(site, layout, data, collection_name)
|
|
98
|
+
site.pages << page
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
Jekyll.logger.info "DynamicPages:", "Generated #{categories.size} category pages for '#{collection_name}'"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Dig into nested hash using dot notation (e.g., "recipe.cuisine")
|
|
105
|
+
def dig_value(hash, field)
|
|
106
|
+
keys = field.split('.')
|
|
107
|
+
value = hash
|
|
108
|
+
keys.each do |key|
|
|
109
|
+
return nil unless value.is_a?(Hash)
|
|
110
|
+
value = value[key]
|
|
111
|
+
end
|
|
112
|
+
value.is_a?(String) ? value : nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def slugify(str)
|
|
116
|
+
str.to_s.downcase.strip.gsub(/[^\w\s-]/, '').gsub(/[\s_]+/, '-')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def titleize(str)
|
|
120
|
+
str.to_s.split(/[\s_-]+/).map(&:capitalize).join(' ')
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def apply_template(template, name, slug)
|
|
124
|
+
template.gsub(':name', name).gsub(':slug', slug)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Custom page class for dynamically generated category pages
|
|
129
|
+
class DynamicCategoryPage < Page
|
|
130
|
+
def initialize(site, layout, data, collection_name)
|
|
131
|
+
@site = site
|
|
132
|
+
@base = site.source
|
|
133
|
+
@dir = ''
|
|
134
|
+
@name = "#{data['slug']}.html"
|
|
135
|
+
|
|
136
|
+
self.process(@name)
|
|
137
|
+
self.data = {
|
|
138
|
+
'layout' => layout,
|
|
139
|
+
'title' => data['title'],
|
|
140
|
+
'description' => data['description'],
|
|
141
|
+
'category_slug' => data['slug'],
|
|
142
|
+
'category_name' => data['name'],
|
|
143
|
+
'collection_name' => collection_name,
|
|
144
|
+
'permalink' => data['permalink'],
|
|
145
|
+
'meta' => {
|
|
146
|
+
'title' => "#{data['title']} - #{site.config.dig('brand', 'name') || site.config['title']}",
|
|
147
|
+
'description' => data['description']
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Limit Collections Generator
|
|
2
|
+
# Limits the number of documents in collections during development for faster builds
|
|
3
|
+
#
|
|
4
|
+
# Usage in _config.dev.yml:
|
|
5
|
+
# limit_collections:
|
|
6
|
+
# recipes: 50
|
|
7
|
+
# products: 20
|
|
8
|
+
#
|
|
9
|
+
# By default, uses a seeded random sample for diverse selection across categories.
|
|
10
|
+
# To disable randomization and take first N documents:
|
|
11
|
+
# limit_collections:
|
|
12
|
+
# recipes: 50
|
|
13
|
+
# products: 20
|
|
14
|
+
# randomize: false
|
|
15
|
+
#
|
|
16
|
+
# Run Jekyll with: bundle exec jekyll serve --config _config.yml,_config.dev.yml
|
|
17
|
+
|
|
18
|
+
module Jekyll
|
|
19
|
+
class LimitCollectionsGenerator < Generator
|
|
20
|
+
safe true
|
|
21
|
+
priority :highest # Run before other generators
|
|
22
|
+
|
|
23
|
+
def generate(site)
|
|
24
|
+
limits = site.config['limit_collections']
|
|
25
|
+
return unless limits.is_a?(Hash)
|
|
26
|
+
|
|
27
|
+
# Check if randomization is disabled (default: true)
|
|
28
|
+
randomize = limits.fetch('randomize', true)
|
|
29
|
+
|
|
30
|
+
limits.each do |collection_name, limit|
|
|
31
|
+
# Skip the 'randomize' option itself
|
|
32
|
+
next if collection_name == 'randomize'
|
|
33
|
+
next unless limit.is_a?(Integer) && limit > 0
|
|
34
|
+
|
|
35
|
+
collection = site.collections[collection_name]
|
|
36
|
+
next unless collection
|
|
37
|
+
|
|
38
|
+
original_count = collection.docs.size
|
|
39
|
+
next if original_count <= limit
|
|
40
|
+
|
|
41
|
+
if randomize
|
|
42
|
+
# Use a seeded random for repeatable but diverse sampling
|
|
43
|
+
# Seed based on collection name so it's consistent across rebuilds
|
|
44
|
+
rng = Random.new(collection_name.hash.abs)
|
|
45
|
+
sampled_docs = collection.docs.shuffle(random: rng).first(limit)
|
|
46
|
+
collection.docs.replace(sampled_docs)
|
|
47
|
+
Jekyll.logger.info "LimitCollections:", "Limited '#{collection_name}' from #{original_count} to #{limit} documents (random sample)"
|
|
48
|
+
else
|
|
49
|
+
# Take first N documents in order
|
|
50
|
+
collection.docs.replace(collection.docs.first(limit))
|
|
51
|
+
Jekyll.logger.info "LimitCollections:", "Limited '#{collection_name}' from #{original_count} to #{limit} documents"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/jekyll-uj-powertools.rb
CHANGED
|
@@ -6,8 +6,10 @@ module Jekyll
|
|
|
6
6
|
require_relative "filters/main"
|
|
7
7
|
|
|
8
8
|
# Load Generators
|
|
9
|
+
require_relative "generators/limit-collections"
|
|
9
10
|
require_relative "generators/inject-properties"
|
|
10
11
|
require_relative "generators/blog-taxonomy"
|
|
12
|
+
require_relative "generators/dynamic-pages"
|
|
11
13
|
|
|
12
14
|
# Load Hooks
|
|
13
15
|
require_relative "hooks/inject-properties"
|
data/lib/tags/logo.rb
CHANGED
|
@@ -5,12 +5,15 @@ require_relative '../helpers/variable_resolver'
|
|
|
5
5
|
module Jekyll
|
|
6
6
|
class UJLogoTag < Liquid::Tag
|
|
7
7
|
include UJPowertools::VariableResolver
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
# Default logo to show when requested logo is not found
|
|
10
10
|
DEFAULT_LOGO = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M320 64C334.7 64 348.2 72.1 355.2 85L571.2 485C577.9 497.4 577.6 512.4 570.4 524.5C563.2 536.6 550.1 544 536 544L104 544C89.9 544 76.9 536.6 69.6 524.5C62.3 512.4 62.1 497.4 68.8 485L284.8 85C291.8 72.1 305.3 64 320 64zM320 232C306.7 232 296 242.7 296 256L296 368C296 381.3 306.7 392 320 392C333.3 392 344 381.3 344 368L344 256C344 242.7 333.3 232 320 232zM346.7 448C347.3 438.1 342.4 428.7 333.9 423.5C325.4 418.4 314.7 418.4 306.2 423.5C297.7 428.7 292.8 438.1 293.4 448C292.8 457.9 297.7 467.3 306.2 472.5C314.7 477.6 325.4 477.6 333.9 472.5C342.4 467.3 347.3 457.9 346.7 448z"/></svg>'
|
|
11
|
-
|
|
12
|
-
# Cache for loaded logos to improve performance
|
|
11
|
+
|
|
12
|
+
# Cache for loaded logos (raw SVG content) to improve performance
|
|
13
13
|
@@logo_cache = {}
|
|
14
|
+
|
|
15
|
+
# Counter for generating unique prefixes per logo instance
|
|
16
|
+
@@instance_counter = 0
|
|
14
17
|
|
|
15
18
|
def initialize(tag_name, markup, tokens)
|
|
16
19
|
super
|
|
@@ -63,22 +66,27 @@ module Jekyll
|
|
|
63
66
|
# Get site from context
|
|
64
67
|
site = context.registers[:site]
|
|
65
68
|
return '' unless site
|
|
66
|
-
|
|
67
|
-
# Load the logo SVG from file
|
|
68
|
-
|
|
69
|
-
return '' unless
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
|
|
69
|
+
|
|
70
|
+
# Load the raw logo SVG from file (cached)
|
|
71
|
+
raw_svg = load_logo_from_file(logo_name.to_s, type, color)
|
|
72
|
+
return '' unless raw_svg
|
|
73
|
+
|
|
74
|
+
# Generate unique prefix for this instance to prevent ID conflicts
|
|
75
|
+
# when the same logo is used multiple times on a page
|
|
76
|
+
@@instance_counter += 1
|
|
77
|
+
unique_prefix = "#{logo_name}-#{@@instance_counter}"
|
|
78
|
+
|
|
79
|
+
# Prefix all IDs with unique prefix
|
|
80
|
+
prefix_svg_ids(raw_svg, unique_prefix)
|
|
73
81
|
end
|
|
74
|
-
|
|
82
|
+
|
|
75
83
|
private
|
|
76
|
-
|
|
84
|
+
|
|
77
85
|
def load_logo_from_file(logo_name, type, color)
|
|
78
86
|
# Create cache key
|
|
79
87
|
cache_key = "#{type}/#{color}/#{logo_name}"
|
|
80
88
|
|
|
81
|
-
# Return cached version if available
|
|
89
|
+
# Return cached version if available (raw SVG without prefixing)
|
|
82
90
|
return @@logo_cache[cache_key] if @@logo_cache.key?(cache_key)
|
|
83
91
|
|
|
84
92
|
# Build file path
|
|
@@ -91,10 +99,7 @@ module Jekyll
|
|
|
91
99
|
DEFAULT_LOGO
|
|
92
100
|
end
|
|
93
101
|
|
|
94
|
-
#
|
|
95
|
-
logo_svg = prefix_svg_ids(logo_svg, logo_name)
|
|
96
|
-
|
|
97
|
-
# Cache the result
|
|
102
|
+
# Cache the raw result (before prefixing)
|
|
98
103
|
@@logo_cache[cache_key] = logo_svg
|
|
99
104
|
return logo_svg
|
|
100
105
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-uj-powertools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ITW Creative Works
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -115,7 +115,9 @@ files:
|
|
|
115
115
|
- jekyll-uj-powertools.gemspec
|
|
116
116
|
- lib/filters/main.rb
|
|
117
117
|
- lib/generators/blog-taxonomy.rb
|
|
118
|
+
- lib/generators/dynamic-pages.rb
|
|
118
119
|
- lib/generators/inject-properties.rb
|
|
120
|
+
- lib/generators/limit-collections.rb
|
|
119
121
|
- lib/helpers/variable_resolver.rb
|
|
120
122
|
- lib/hooks/inject-properties.rb
|
|
121
123
|
- lib/hooks/markdown-images.rb
|