jekyll-uj-powertools 1.6.24 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e244425c0a57091b17d74eeb82b585314a5892bc4d2185e6dc50b5d66d7d16c
4
- data.tar.gz: e999ee1ea01bee859c675e451c3d8323c984b225a7ae2438f3d06f1f43158848
3
+ metadata.gz: 60803db8fb2a1e2c50aea11a0f49e964fc16d3323943ce0330f4312fd6468e37
4
+ data.tar.gz: 05f5ac27b7651b0d5592395cc510ba5b31e77f9b5ead9f35f0de6420d5919fd9
5
5
  SHA512:
6
- metadata.gz: 4042544bbfb0c7e818b9d56ab22adb617c0e5ab74f5284e03eaab1c2a5fed114b77e3d7860759cdef9d0ba9d057b542e5e916f690bda237defbf7426056fcff7
7
- data.tar.gz: cd588783c0bd5b02069eb47363352d6200d03de8b4208004a34a23db7372a123dffc47b2138174e7969f4380cf39f649ac977c06a41e6dfbedd7469592732a5f
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
 
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  Gem::Specification.new do |spec|
6
6
  # Gem info
7
7
  spec.name = "jekyll-uj-powertools"
8
- spec.version = "1.6.24"
8
+ spec.version = "1.7.0"
9
9
 
10
10
  # Author info
11
11
  spec.authors = ["ITW Creative Works"]
@@ -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
@@ -5,7 +5,7 @@
5
5
  module Jekyll
6
6
  class InjectProperties < Generator
7
7
  safe true
8
- priority :low
8
+ priority :lowest # Run LAST so all pages from other generators exist before injecting resolved data
9
9
 
10
10
  def generate(site)
11
11
  # Define a global variable accessible in templates
@@ -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
@@ -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"
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.6.24
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-17 00:00:00.000000000 Z
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