jekyll-uj-powertools 1.6.0 → 1.6.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/README.md +94 -0
- data/jekyll-uj-powertools.gemspec +2 -1
- data/lib/filters/main.rb +36 -18
- data/lib/generators/inject-properties.rb +23 -1
- data/lib/hooks/inject-properties.rb +10 -0
- data/lib/hooks/markdown-images.rb +40 -0
- data/lib/jekyll-uj-powertools.rb +12 -1
- data/lib/tags/fake_comments.rb +72 -0
- data/lib/tags/icon.rb +262 -0
- data/lib/tags/iffile.rb +70 -0
- data/lib/tags/image.rb +208 -0
- data/lib/tags/language.rb +301 -0
- data/lib/tags/member.rb +204 -0
- data/lib/tags/post.rb +258 -0
- data/lib/tags/readtime.rb +73 -0
- data/lib/tags/social.rb +84 -0
- data/lib/tags/translation_url.rb +154 -0
- metadata +27 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 035a3dbc797be68fa2e9dd84f80417b63672994e25362ce47897a254da7faa12
|
4
|
+
data.tar.gz: b7e4edc28279b80e0aadcae6435dd1283517d6f3aa68b9f65dfe99edfe0ae94a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c7c040655729bec85fc2c08b36bc84a759b518884c5f408bd75b0b735d05fdfee891b182f24b4b1f53004fe002bc7e23e31ff20fa6722871c82b83fe12964e1
|
7
|
+
data.tar.gz: 8ebda7bd0080113e1496fa8092889b79f80573a2c8241df1e07708b95e581dd1c143769561c295254070edcf40709cf3a5b48ba3e6965e5a3db524774e33ca0e
|
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 `` 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.
|
@@ -143,6 +170,73 @@ A custom Liquid tag that checks if a variable is falsy (nil, false, empty string
|
|
143
170
|
{% endifalsy %}
|
144
171
|
```
|
145
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
|
+
|
146
240
|
## Final notes
|
147
241
|
These examples show how you can use the features of `jekyll-uj-powertools` in your Jekyll site.
|
148
242
|
|
@@ -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.
|
8
|
+
spec.version = "1.6.2"
|
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
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
|
-
#
|
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
|
-
|
97
|
-
|
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
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
# Generator
|
5
5
|
module Jekyll
|
6
|
-
class
|
6
|
+
class InjectProperties < Generator
|
7
7
|
safe true
|
8
8
|
priority :low
|
9
9
|
|
@@ -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
|
data/lib/jekyll-uj-powertools.rb
CHANGED
@@ -10,8 +10,19 @@ 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/
|
16
|
+
require_relative "tags/fake_comments"
|
17
|
+
require_relative "tags/icon"
|
16
18
|
require_relative "tags/iffalsy"
|
19
|
+
require_relative "tags/iffile"
|
20
|
+
require_relative "tags/iftruthy"
|
21
|
+
require_relative "tags/image"
|
22
|
+
require_relative "tags/language"
|
23
|
+
require_relative "tags/member"
|
24
|
+
require_relative "tags/post"
|
25
|
+
require_relative "tags/readtime"
|
26
|
+
require_relative "tags/social"
|
27
|
+
require_relative "tags/translation_url"
|
17
28
|
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)
|
data/lib/tags/icon.rb
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
# Libraries
|
2
|
+
require "jekyll"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
class UJIconTag < Liquid::Tag
|
6
|
+
# Default icon to show when requested icon is not found
|
7
|
+
DEFAULT_ICON = '<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>'
|
8
|
+
|
9
|
+
# Language code to country code mapping for flags
|
10
|
+
LANGUAGE_TO_COUNTRY = {
|
11
|
+
'en' => 'us', # English -> United States (could also be 'gb' for Great Britain)
|
12
|
+
'es' => 'es', # Spanish -> Spain
|
13
|
+
'fr' => 'fr', # French -> France
|
14
|
+
'de' => 'de', # German -> Germany
|
15
|
+
'it' => 'it', # Italian -> Italy
|
16
|
+
'pt' => 'pt', # Portuguese -> Portugal
|
17
|
+
'ru' => 'ru', # Russian -> Russia
|
18
|
+
'ja' => 'jp', # Japanese -> Japan
|
19
|
+
'ko' => 'kr', # Korean -> South Korea
|
20
|
+
'zh' => 'cn', # Chinese -> China
|
21
|
+
'ar' => 'sa', # Arabic -> Saudi Arabia
|
22
|
+
'hi' => 'in', # Hindi -> India
|
23
|
+
'tr' => 'tr', # Turkish -> Turkey
|
24
|
+
'pl' => 'pl', # Polish -> Poland
|
25
|
+
'nl' => 'nl', # Dutch -> Netherlands
|
26
|
+
'sv' => 'se', # Swedish -> Sweden
|
27
|
+
'no' => 'no', # Norwegian -> Norway
|
28
|
+
'da' => 'dk', # Danish -> Denmark
|
29
|
+
'fi' => 'fi', # Finnish -> Finland
|
30
|
+
'he' => 'il', # Hebrew -> Israel
|
31
|
+
'th' => 'th', # Thai -> Thailand
|
32
|
+
'vi' => 'vn', # Vietnamese -> Vietnam
|
33
|
+
'uk' => 'ua', # Ukrainian -> Ukraine
|
34
|
+
'cs' => 'cz', # Czech -> Czech Republic
|
35
|
+
'hu' => 'hu', # Hungarian -> Hungary
|
36
|
+
'ro' => 'ro', # Romanian -> Romania
|
37
|
+
'bg' => 'bg', # Bulgarian -> Bulgaria
|
38
|
+
'hr' => 'hr', # Croatian -> Croatia
|
39
|
+
'sk' => 'sk', # Slovak -> Slovakia
|
40
|
+
'sl' => 'si', # Slovenian -> Slovenia
|
41
|
+
'et' => 'ee', # Estonian -> Estonia
|
42
|
+
'lv' => 'lv', # Latvian -> Latvia
|
43
|
+
'lt' => 'lt', # Lithuanian -> Lithuania
|
44
|
+
'mt' => 'mt', # Maltese -> Malta
|
45
|
+
'ga' => 'ie', # Irish -> Ireland
|
46
|
+
'cy' => 'gb', # Welsh -> Great Britain
|
47
|
+
'ca' => 'es', # Catalan -> Spain (could also be ad for Andorra)
|
48
|
+
'eu' => 'es', # Basque -> Spain
|
49
|
+
'gl' => 'es', # Galician -> Spain
|
50
|
+
}
|
51
|
+
|
52
|
+
# Font Awesome size mappings - commented out for now
|
53
|
+
# FA_SIZES = {
|
54
|
+
# 'fa-2xs' => '0.625em',
|
55
|
+
# 'fa-xs' => '0.75em',
|
56
|
+
# 'fa-sm' => '0.875em',
|
57
|
+
# 'fa-md' => '1em',
|
58
|
+
# 'fa-lg' => '1.25em',
|
59
|
+
# 'fa-xl' => '1.5em',
|
60
|
+
# 'fa-2xl' => '2em'
|
61
|
+
# }
|
62
|
+
|
63
|
+
# Cache for loaded icons to improve performance
|
64
|
+
@@icon_cache = {}
|
65
|
+
|
66
|
+
def initialize(tag_name, markup, tokens)
|
67
|
+
super
|
68
|
+
@markup = markup.strip
|
69
|
+
end
|
70
|
+
|
71
|
+
def render(context)
|
72
|
+
# Parse arguments that can be quoted or unquoted
|
73
|
+
parts = parse_arguments(@markup)
|
74
|
+
icon_name_input = parts[0]
|
75
|
+
css_classes = parts[1]
|
76
|
+
|
77
|
+
# Check if the input was originally quoted (literal string)
|
78
|
+
is_quoted = @markup.strip.match(/^['"]/)
|
79
|
+
|
80
|
+
# If quoted, use as literal. Otherwise, try to resolve as variable
|
81
|
+
if is_quoted
|
82
|
+
icon_name = icon_name_input
|
83
|
+
else
|
84
|
+
# Try to resolve as a variable
|
85
|
+
icon_name = resolve_variable(context, icon_name_input)
|
86
|
+
# If it didn't resolve to a string, use the input as literal
|
87
|
+
icon_name = icon_name_input if icon_name.nil? || !icon_name.is_a?(String)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Strip quotes from resolved icon name if present
|
91
|
+
if icon_name.is_a?(String) && icon_name.match(/^['"].*['"]$/)
|
92
|
+
icon_name = icon_name[1..-2]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Get site from context
|
96
|
+
site = context.registers[:site]
|
97
|
+
return '' unless site
|
98
|
+
|
99
|
+
# Load the icon SVG from file
|
100
|
+
icon_svg = load_icon_from_file(site, icon_name.to_s)
|
101
|
+
return '' unless icon_svg
|
102
|
+
|
103
|
+
# Process SVG to inject required attributes
|
104
|
+
processed_svg = inject_svg_attributes(icon_svg)
|
105
|
+
|
106
|
+
# Determine CSS classes
|
107
|
+
# font_size = '1em' # default
|
108
|
+
# if size_input && !size_input.empty?
|
109
|
+
# # Check if it's a Font Awesome preset size
|
110
|
+
# font_size = FA_SIZES[size_input] || size_input
|
111
|
+
# end
|
112
|
+
|
113
|
+
# Wrap in i tag with CSS classes (always include 'fa' class)
|
114
|
+
if css_classes && !css_classes.empty?
|
115
|
+
"<i class=\"fa #{css_classes}\">#{processed_svg}</i>"
|
116
|
+
else
|
117
|
+
"<i class=\"fa\">#{processed_svg}</i>"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def inject_svg_attributes(svg_content)
|
124
|
+
# Inject width, height, and fill attributes into the SVG tag
|
125
|
+
if svg_content.include?('<svg')
|
126
|
+
# Replace the opening SVG tag to include our required attributes
|
127
|
+
svg_content.sub(/<svg([^>]*)>/) do |match|
|
128
|
+
existing_attrs = $1
|
129
|
+
# Only add attributes if they don't already exist
|
130
|
+
attrs_to_add = []
|
131
|
+
attrs_to_add << 'width="1em"' unless existing_attrs.include?('width=')
|
132
|
+
attrs_to_add << 'height="1em"' unless existing_attrs.include?('height=')
|
133
|
+
attrs_to_add << 'fill="currentColor"' unless existing_attrs.include?('fill=')
|
134
|
+
|
135
|
+
if attrs_to_add.any?
|
136
|
+
"<svg#{existing_attrs} #{attrs_to_add.join(' ')}>"
|
137
|
+
else
|
138
|
+
match
|
139
|
+
end
|
140
|
+
end
|
141
|
+
else
|
142
|
+
svg_content
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def load_icon_from_file(site, icon_name)
|
147
|
+
# Get the style from site config
|
148
|
+
style = site.config.dig('icons', 'style') || 'solid'
|
149
|
+
|
150
|
+
# Create cache key
|
151
|
+
cache_key = "#{style}/#{icon_name}"
|
152
|
+
|
153
|
+
# Return cached version if available
|
154
|
+
return @@icon_cache[cache_key] if @@icon_cache.key?(cache_key)
|
155
|
+
|
156
|
+
# Try to load icon from multiple sources in order
|
157
|
+
icon_svg = try_load_fontawesome_icon(icon_name, style) ||
|
158
|
+
try_load_flag_icon(icon_name) ||
|
159
|
+
DEFAULT_ICON
|
160
|
+
|
161
|
+
# Cache the result
|
162
|
+
@@icon_cache[cache_key] = icon_svg
|
163
|
+
return icon_svg
|
164
|
+
end
|
165
|
+
|
166
|
+
def try_load_fontawesome_icon(icon_name, style)
|
167
|
+
# Build file path for the configured style
|
168
|
+
icon_path = File.join(Dir.pwd, 'node_modules', 'ultimate-jekyll-manager', 'assets', 'icons', 'font-awesome', style, "#{icon_name}.svg")
|
169
|
+
|
170
|
+
# Read file if it exists in the configured style
|
171
|
+
if File.exist?(icon_path)
|
172
|
+
return File.read(icon_path)
|
173
|
+
end
|
174
|
+
|
175
|
+
# If not found and style is not 'brands', try brands style as fallback
|
176
|
+
if style != 'brands'
|
177
|
+
brands_path = File.join(Dir.pwd, 'node_modules', 'ultimate-jekyll-manager', 'assets', 'icons', 'font-awesome', 'brands', "#{icon_name}.svg")
|
178
|
+
|
179
|
+
if File.exist?(brands_path)
|
180
|
+
return File.read(brands_path)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
|
187
|
+
def try_load_flag_icon(icon_name)
|
188
|
+
# First try direct country code (e.g., 'us', 'gb')
|
189
|
+
flag_path = File.join(Dir.pwd, 'node_modules', 'ultimate-jekyll-manager', 'assets', 'icons', 'flags', 'modern-square', "#{icon_name}.svg")
|
190
|
+
|
191
|
+
if File.exist?(flag_path)
|
192
|
+
return File.read(flag_path)
|
193
|
+
end
|
194
|
+
|
195
|
+
# If not found, try language code to country code mapping (e.g., 'en' -> 'us')
|
196
|
+
country_code = LANGUAGE_TO_COUNTRY[icon_name.downcase]
|
197
|
+
if country_code
|
198
|
+
mapped_flag_path = File.join(Dir.pwd, 'node_modules', 'ultimate-jekyll-manager', 'assets', 'icons', 'flags', 'modern-square', "#{country_code}.svg")
|
199
|
+
|
200
|
+
if File.exist?(mapped_flag_path)
|
201
|
+
return File.read(mapped_flag_path)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
|
208
|
+
def parse_arguments(markup)
|
209
|
+
# Parse arguments that can be quoted or unquoted
|
210
|
+
# Examples: award, fa-md OR 'award', 'fa-md' OR myVar, "2em"
|
211
|
+
args = []
|
212
|
+
current_arg = ''
|
213
|
+
in_quotes = false
|
214
|
+
quote_char = nil
|
215
|
+
|
216
|
+
markup.each_char.with_index do |char, i|
|
217
|
+
if !in_quotes && (char == '"' || char == "'")
|
218
|
+
# Start of quoted string
|
219
|
+
in_quotes = true
|
220
|
+
quote_char = char
|
221
|
+
elsif in_quotes && char == quote_char
|
222
|
+
# End of quoted string
|
223
|
+
in_quotes = false
|
224
|
+
quote_char = nil
|
225
|
+
elsif !in_quotes && char == ','
|
226
|
+
# Argument separator
|
227
|
+
args << current_arg.strip
|
228
|
+
current_arg = ''
|
229
|
+
else
|
230
|
+
# Regular character
|
231
|
+
current_arg += char
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Add the last argument
|
236
|
+
args << current_arg.strip if current_arg.strip.length > 0
|
237
|
+
|
238
|
+
args
|
239
|
+
end
|
240
|
+
|
241
|
+
def resolve_variable(context, variable_name)
|
242
|
+
# Handle nested variable access like page.icon
|
243
|
+
parts = variable_name.split('.')
|
244
|
+
current = context
|
245
|
+
|
246
|
+
parts.each do |part|
|
247
|
+
if current.respond_to?(:[])
|
248
|
+
current = current[part]
|
249
|
+
elsif current.respond_to?(:key?) && current.key?(part)
|
250
|
+
current = current[part]
|
251
|
+
else
|
252
|
+
return nil
|
253
|
+
end
|
254
|
+
return nil if current.nil?
|
255
|
+
end
|
256
|
+
|
257
|
+
current
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
Liquid::Template.register_tag('uj_icon', Jekyll::UJIconTag)
|