jekyll-uj-powertools 1.5.2 → 1.6.1

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: 4c546d3b5f77c67bf33fdcdbecfeb0c741760a89fdcf508f05caf7943b19b197
4
- data.tar.gz: 2612f6dae15afb265e3193bccdaf038421eb1ad51e3bdd955c0ce610b324e441
3
+ metadata.gz: 4457cc2876947bfe080aa15d492f502208e3dd64573c71e11eb3735cf6a6a6b7
4
+ data.tar.gz: d8cbd12fcdccc5aedad51ded42d41d03a86cbc92610b722705b16ceafffc50c9
5
5
  SHA512:
6
- metadata.gz: e95b14dd432df58e934bb9cab3c77eb8bb577cc6e917475692753ec95b9d78ed8759e4310c6560fa6bb5da350519eb3e3fe97fc3278c78c2b345e021a53fc256
7
- data.tar.gz: 54646abcd5832dabb835cdfff9dfb71d80aebe28df1727381497beb7af057ef594a05231cc9327ddf87f74484c1c6035d440a394b5fd97568943e641999f9af8
6
+ metadata.gz: 9e6058f9e42984ad01939d759cc706daaf1af6a88bea81d33fd30815ca34ba05a48e2491f5b40abb8548990f11122f60bf190b1cdc6daae8b2a2a0d4d9d526b3
7
+ data.tar.gz: 80348c6d1d507ae4301cf48e4b862e8687d404260edd9c0fbeb34633c7c23993a18dbde89d54e5d3dcf83052fd88563cec6b76a7e1637bb6573c5647f469f48a
data/README.md CHANGED
@@ -70,6 +70,33 @@ Convert a string to title case.
70
70
  {{ "hello world" | uj_title_case }}
71
71
  ```
72
72
 
73
+ ### `uj_content_format` Filter
74
+ Process content with Liquid templating and Markdown conversion, automatically transforming markdown images to responsive `uj_image` tags.
75
+
76
+ ```liquid
77
+ {{ post.content | uj_content_format }}
78
+ ```
79
+
80
+ This filter:
81
+ - Transforms markdown images `![alt](url)` to `{% uj_image "url", alt="alt", class="..." %}`
82
+ - Automatically pulls image class from `page.resolved.theme.blog.image.class`
83
+ - Processes Liquid tags in the content
84
+ - Converts Markdown to HTML (for .md files)
85
+
86
+ If no class is specified in frontmatter, the `uj_image` tag will be rendered without a class attribute.
87
+
88
+ #### Frontmatter Configuration Example
89
+ ```yaml
90
+ ---
91
+ theme:
92
+ blog:
93
+ image:
94
+ class: "img-fluid rounded-3 shadow"
95
+ ---
96
+ ```
97
+
98
+ With this frontmatter, all markdown images in the post will automatically use the specified class.
99
+
73
100
  ## Global Variables
74
101
  ### `site.uj.cache_breaker` Variable
75
102
  Use the `site.uj.cache_breaker` variable to append a cache-busting query parameter to your assets.
@@ -78,7 +105,7 @@ Use the `site.uj.cache_breaker` variable to append a cache-busting query paramet
78
105
  <link rel="stylesheet" href="{{ "/assets/css/style.css" | prepend: site.baseurl }}?v={{ site.uj.cache_breaker }}">
79
106
  ```
80
107
 
81
- ### Page Variables
108
+ ## Page Variables
82
109
  ### `page.random_id` Variable
83
110
  Generate a random ID for each page, useful for sorting randomly or for unique identifiers.
84
111
 
@@ -126,6 +153,91 @@ Resolves the site, layout, and page data into a single object, which can be usef
126
153
  {{ page.my.variable | default: layout.my.variable | default: site.my.variable }}
127
154
  ```
128
155
 
156
+ ## Tags
157
+ ### `iftruthy` Tag
158
+ A custom Liquid tag that checks if a variable is truthy (not nil, not false, not empty string, not 0) and renders the content inside the tag if it is truthy.
159
+ ```liquid
160
+ {% iftruthy my_variable %}
161
+ <p>This content will only be rendered if my_variable is truthy.</p>
162
+ {% endiftruthy %}
163
+ ```
164
+
165
+ ### `iffalsy` Tag
166
+ A custom Liquid tag that checks if a variable is falsy (nil, false, empty string, or 0) and renders the content inside the tag if it is falsy.
167
+ ```liquid
168
+ {% iffalsy my_variable %}
169
+ <p>This content will only be rendered if my_variable is falsy.</p>
170
+ {% endifalsy %}
171
+ ```
172
+
173
+ ### `uj_icon` Tag
174
+ A custom Liquid tag that renders a Font Awesome icon with the specified style and name. It supports `name` and `class` parameters.
175
+ ```liquid
176
+ {% uj_icon "rocket", "fa-lg me-2" %}
177
+ ```
178
+
179
+ ### `uj_fake_comments` Tag
180
+ Generates a fake comment count based on content word count for demonstration purposes.
181
+ ```liquid
182
+ {% uj_fake_comments %}
183
+ {% uj_fake_comments page.content %}
184
+ ```
185
+
186
+ ### `uj_image` Tag
187
+ Renders responsive images with WebP support and lazy loading.
188
+ ```liquid
189
+ {% uj_image "/assets/images/hero.jpg", max_width="1024", alt="Hero image" %}
190
+ {% uj_image page.featured_image, class="img-fluid", webp="false" %}
191
+ ```
192
+
193
+ ### `uj_language` Tag
194
+ Converts ISO language codes to language names in English or native format.
195
+ ```liquid
196
+ {% uj_language "es" %}
197
+ {% uj_language page.language, "native" %}
198
+ ```
199
+
200
+ ### `uj_member` Tag
201
+ Retrieves member information from site team collection.
202
+ ```liquid
203
+ {% uj_member "john-doe", "name" %}
204
+ {% uj_member page.author, "url" %}
205
+ {% uj_member member_id, "image" %}
206
+ {% uj_member "john-doe", "image-tag", max_width="640", class="team-photo" %}
207
+ ```
208
+
209
+ The `image-tag` property renders a responsive image using the `uj_image` tag with all its features (WebP, lazy loading, responsive sizes). You can pass any `uj_image` options as additional parameters.
210
+
211
+ ### `uj_post` Tag
212
+ Fetches post data from site collections.
213
+ ```liquid
214
+ {% uj_post "my-post-slug", "title" %}
215
+ {% uj_post post.id, "description" %}
216
+ {% uj_post current_post, "image-url" %}
217
+ ```
218
+
219
+ ### `uj_readtime` Tag
220
+ Calculates estimated reading time based on content (200 words per minute).
221
+ ```liquid
222
+ {% uj_readtime %}
223
+ {% uj_readtime page.content %}
224
+ ```
225
+
226
+ ### `uj_social` Tag
227
+ Generates social media URLs from platform handles.
228
+ ```liquid
229
+ {% uj_social "twitter" %}
230
+ {% uj_social "github" %}
231
+ ```
232
+
233
+ ### `uj_translation_url` Tag
234
+ Creates language-specific URLs for multilingual sites.
235
+ ```liquid
236
+ {% uj_translation_url "es", page.url %}
237
+ {% uj_translation_url target_lang, "/pricing" %}
238
+ ```
239
+
240
+ ## Final notes
129
241
  These examples show how you can use the features of `jekyll-uj-powertools` in your Jekyll site.
130
242
 
131
243
  ## 🔧 Development
@@ -133,9 +245,6 @@ After checking out the repo, run `bin/setup` to install dependencies. You can al
133
245
 
134
246
  To install this gem onto your local machine, run `bundle exec rake install`.
135
247
 
136
-
137
-
138
-
139
248
  ## ⚠️ Testing
140
249
  Run the tests
141
250
  ```shell
@@ -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.5.2"
8
+ spec.version = "1.6.1"
9
9
 
10
10
  # Author info
11
11
  spec.authors = ["ITW Creative Works"]
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "bundler"
31
31
  spec.add_development_dependency "rake"
32
32
  spec.add_development_dependency "rspec"
33
+ spec.add_development_dependency "simplecov"
33
34
 
34
35
  # Translation and HTML manipulation requires Nokogiri
35
36
  spec.add_runtime_dependency 'nokogiri', '>= 1.17'
data/lib/filters/main.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # Libraries
2
2
  require "jekyll"
3
+ require "json"
3
4
 
4
- # Module
5
+ # Filters
5
6
  module Jekyll
6
7
  module UJPowertools
7
- # Initialize a timestamp that will remain consistent across calls
8
- @cache_timestamp = Time.now.to_i.to_s
8
+ # Initialize a timestamp that will remain consistent across calls (with milliseconds)
9
+ @cache_timestamp = (Time.now.to_f * 1000).to_i.to_s
9
10
 
10
11
  # Strip ads from the input
11
12
  def uj_strip_ads(input)
@@ -48,11 +49,6 @@ module Jekyll
48
49
  rand(input)
49
50
  end
50
51
 
51
- # Return the current year
52
- def uj_year(input)
53
- Time.now.year
54
- end
55
-
56
52
  # Title case
57
53
  def uj_title_case(input)
58
54
  input.split(' ').map(&:capitalize).join(' ')
@@ -79,11 +75,18 @@ module Jekyll
79
75
 
80
76
  # Format content based on file extension - apply liquify and markdownify for .md files
81
77
  def uj_content_format(input)
78
+ # Return empty string if input is nil
79
+ return '' unless input
80
+
82
81
  # Get the current page from context
83
82
  page = @context.registers[:page] if @context.respond_to?(:registers)
84
83
  page ||= @context[:registers][:page] if @context.is_a?(Hash)
84
+
85
+ # Get site from context
86
+ site = @context.registers[:site] if @context.respond_to?(:registers)
87
+ site ||= @context[:registers][:site] if @context.is_a?(Hash)
85
88
 
86
- # Apply liquify first
89
+ # Simply apply liquify first (markdown images are already converted to uj_image tags by the hook)
87
90
  liquified = if @context.respond_to?(:registers)
88
91
  Liquid::Template.parse(input).render(@context)
89
92
  else
@@ -91,22 +94,37 @@ module Jekyll
91
94
  end
92
95
 
93
96
  # Check if the page extension is .md
94
- if page && page['extension'] == '.md'
97
+ if page && page['extension'] == '.md' && site
95
98
  # Apply markdownify for markdown files
96
- site = @context.registers[:site] if @context.respond_to?(:registers)
97
- site ||= @context[:registers][:site] if @context.is_a?(Hash)
98
-
99
- if site
100
- converter = site.find_converter_instance(Jekyll::Converters::Markdown)
101
- converter.convert(liquified)
102
- else
103
- liquified
104
- end
99
+ converter = site.find_converter_instance(Jekyll::Converters::Markdown)
100
+ converter.convert(liquified)
105
101
  else
106
102
  # Return just liquified content for non-markdown files
107
103
  liquified
108
104
  end
109
105
  end
106
+
107
+ # Pretty print JSON with configurable indentation (default 2 spaces)
108
+ def uj_jsonify(input, indent_size = 2)
109
+ indent_string = ' ' * indent_size.to_i
110
+ JSON.pretty_generate(input, indent: indent_string)
111
+ end
112
+
113
+ private
114
+
115
+ # Helper method to safely dig through nested hashes
116
+ def dig_value(hash, *keys)
117
+ return nil unless hash
118
+
119
+ value = hash
120
+ keys.each do |key|
121
+ return nil unless value.is_a?(Hash)
122
+ value = value[key]
123
+ return nil if value.nil?
124
+ end
125
+
126
+ value
127
+ end
110
128
  end
111
129
  end
112
130
 
@@ -1,9 +1,9 @@
1
1
  # Libraries
2
2
  # ...
3
3
 
4
- # Module
4
+ # Generator
5
5
  module Jekyll
6
- class InjectData < Generator
6
+ class InjectProperties < Generator
7
7
  safe true
8
8
  priority :low
9
9
 
@@ -75,16 +75,16 @@ module Jekyll
75
75
  def get_layout_chain(layout_name, site)
76
76
  chain = []
77
77
  current_layout_name = layout_name
78
-
78
+
79
79
  # Traverse up the layout hierarchy
80
80
  while current_layout_name
81
81
  layout = site.layouts[current_layout_name]
82
82
  break unless layout
83
-
83
+
84
84
  chain.unshift(layout) # Add to beginning to maintain parent->child order
85
85
  current_layout_name = layout.data['layout']
86
86
  end
87
-
87
+
88
88
  chain
89
89
  end
90
90
 
@@ -95,13 +95,13 @@ module Jekyll
95
95
  'path', 'relative_path', 'collection', 'type', 'id', 'url',
96
96
  'next', 'previous', 'draft', 'ext', 'excerpt', 'output'
97
97
  ]
98
-
98
+
99
99
  filtered = {}
100
100
  data.each do |key, value|
101
101
  next if jekyll_internals.include?(key)
102
102
  filtered[key] = value
103
103
  end
104
-
104
+
105
105
  filtered
106
106
  end
107
107
 
@@ -114,6 +114,27 @@ module Jekyll
114
114
  item.data['extension'] = File.extname(item.path)
115
115
  end
116
116
 
117
+
118
+ # Inject canonical URL
119
+ if item.respond_to?(:url)
120
+ page_url_stripped = item.url.sub(/index\.html$/, '')
121
+ page_url_stripped = '' if page_url_stripped == '/'
122
+ site_url = site.config['url'] || ''
123
+ item.data['canonical'] = {
124
+ 'url' => site_url + page_url_stripped,
125
+ 'path' => page_url_stripped.empty? ? '/' : page_url_stripped
126
+ }
127
+ end
128
+
129
+ # Inject page type based on post or member properties
130
+ if item.data['post']
131
+ item.data['type'] = 'post'
132
+ elsif item.data['member']
133
+ item.data['type'] = 'member'
134
+ else
135
+ item.data['type'] = 'basic'
136
+ end
137
+
117
138
  # Set resolved data for site, layout, and page
118
139
  # Create a deep merge of site -> child layouts -> parent layouts -> page data
119
140
  # Priority: page (highest) -> parent layouts -> child layouts -> site (lowest)
@@ -155,5 +176,6 @@ module Jekyll
155
176
  # Add the resolved data to the item
156
177
  item.data['resolved'] = resolved
157
178
  end
179
+
158
180
  end
159
181
  end
@@ -3,6 +3,16 @@
3
3
 
4
4
  # Hook
5
5
  Jekyll::Hooks.register :site, :pre_render do |site|
6
+ # Ensure uj config exists
6
7
  site.config['uj'] ||= {}
8
+
9
+ # Set cache breaker
7
10
  site.config['uj']['cache_breaker'] = Jekyll::UJPowertools.cache_timestamp
11
+
12
+ # Add date properties
13
+ site.config['uj']['date'] ||= {}
14
+ now = Time.now
15
+ site.config['uj']['date']['year'] = now.year
16
+ site.config['uj']['date']['month'] = now.month
17
+ site.config['uj']['date']['day'] = now.day
8
18
  end
@@ -0,0 +1,40 @@
1
+ # Libraries
2
+ require "jekyll"
3
+
4
+ module Jekyll
5
+ # Hook into the pre_render phase to transform markdown images before conversion
6
+ Jekyll::Hooks.register [:posts, :pages, :documents], :pre_render do |doc|
7
+ # Only process markdown files
8
+ if doc.extname == ".md"
9
+ # Get image class from resolved data if available
10
+ image_class = nil
11
+ if doc.data['resolved'] && doc.data['resolved']['theme']
12
+ theme = doc.data['resolved']['theme']
13
+ if theme['post'] && theme['post']['image'] && theme['post']['image']['class']
14
+ image_class = theme['post']['image']['class']
15
+ end
16
+ end
17
+
18
+ # Transform markdown images by parsing and rendering Liquid template
19
+ doc.content = doc.content.gsub(/!\[([^\]]*)\]\(([^)]+)\)/) do
20
+ alt_text = $1
21
+ image_path = $2
22
+
23
+ # Build the Liquid tag string
24
+ if image_class
25
+ liquid_tag = "{% uj_image \"#{image_path}\", alt=\"#{alt_text}\", class=\"#{image_class}\" %}"
26
+ else
27
+ liquid_tag = "{% uj_image \"#{image_path}\", alt=\"#{alt_text}\" %}"
28
+ end
29
+
30
+ # Parse and render the Liquid template immediately
31
+ template = Liquid::Template.parse(liquid_tag)
32
+ context = doc.site.site_payload.merge({'page' => doc.to_liquid})
33
+ result = template.render(Liquid::Context.new(context))
34
+
35
+ # Return the HTML with blank lines to ensure markdown treats it as raw HTML
36
+ "\n\n#{result}\n\n"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -10,7 +10,18 @@ module Jekyll
10
10
 
11
11
  # Load Hooks
12
12
  require_relative "hooks/inject-properties"
13
+ require_relative "hooks/markdown-images"
13
14
 
14
15
  # Load Tags
15
- # require_relative "tags/ifistruthy"
16
+ require_relative "tags/iftruthy"
17
+ require_relative "tags/iffalsy"
18
+ require_relative "tags/icon"
19
+ require_relative "tags/social"
20
+ require_relative "tags/readtime"
21
+ require_relative "tags/fake_comments"
22
+ require_relative "tags/member"
23
+ require_relative "tags/image"
24
+ require_relative "tags/post"
25
+ require_relative "tags/language"
26
+ require_relative "tags/translation_url"
16
27
  end
@@ -0,0 +1,72 @@
1
+ # Libraries
2
+ require "jekyll"
3
+
4
+ module Jekyll
5
+ class UJCommentsTag < Liquid::Tag
6
+ def initialize(tag_name, markup, tokens)
7
+ super
8
+ @markup = markup.strip
9
+ end
10
+
11
+ def render(context)
12
+ # Get the content to analyze
13
+ content = resolve_content(context)
14
+ return '0' unless content
15
+
16
+ # Strip HTML tags
17
+ stripped_content = strip_html(content)
18
+
19
+ # Count words
20
+ words = count_words(stripped_content)
21
+
22
+ # Generate comment count based on word count modulo 13
23
+ comments = words % 13
24
+
25
+ comments.to_s
26
+ end
27
+
28
+ private
29
+
30
+ def resolve_content(context)
31
+ if @markup.empty?
32
+ # No argument, use page content
33
+ page = context['page']
34
+ return nil unless page
35
+ page['content']
36
+ else
37
+ # Resolve the variable name
38
+ resolve_variable(context, @markup)
39
+ end
40
+ end
41
+
42
+ def resolve_variable(context, variable_name)
43
+ # Handle nested variable access like page.content or include.content
44
+ parts = variable_name.split('.')
45
+ current = context
46
+
47
+ parts.each do |part|
48
+ return nil unless current.respond_to?(:[]) || current.is_a?(Hash)
49
+ current = current[part]
50
+ return nil if current.nil?
51
+ end
52
+
53
+ current
54
+ end
55
+
56
+ def strip_html(content)
57
+ # Remove HTML tags
58
+ content = content.to_s.gsub(/<script.*?<\/script>/m, '')
59
+ content = content.gsub(/<style.*?<\/style>/m, '')
60
+ content = content.gsub(/<[^>]+>/, ' ')
61
+ content = content.gsub(/\s+/, ' ')
62
+ content.strip
63
+ end
64
+
65
+ def count_words(text)
66
+ # Count words (split by whitespace)
67
+ text.split(/\s+/).length
68
+ end
69
+ end
70
+ end
71
+
72
+ Liquid::Template.register_tag('uj_fake_comments', Jekyll::UJCommentsTag)