jekyll-uj-powertools 1.6.0 → 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.
@@ -0,0 +1,204 @@
1
+ # Libraries
2
+ require "jekyll"
3
+
4
+ module Jekyll
5
+ class UJMemberTag < Liquid::Tag
6
+ def initialize(tag_name, markup, tokens)
7
+ super
8
+ @markup = markup.strip
9
+ end
10
+
11
+ def render(context)
12
+ # Parse arguments preserving quotes
13
+ args = parse_arguments_with_quotes(@markup)
14
+ member_input = args[0]
15
+ property_input = args[1] || "'name'" # Default to name if no property specified
16
+
17
+ # Strip quotes from property if present
18
+ property = property_input.gsub(/^['"]|['"]$/, '')
19
+
20
+ # Check if the member input was originally quoted
21
+ is_quoted = member_input && member_input.match(/^['"]/)
22
+
23
+ # Resolve member ID
24
+ if is_quoted
25
+ # If quoted, strip quotes and use as literal
26
+ member_id = member_input.gsub(/^['"]|['"]$/, '')
27
+ else
28
+ # Otherwise resolve as variable
29
+ member_id = resolve_member_id(context, member_input)
30
+ end
31
+ return '' unless member_id
32
+
33
+ # Find member in site.team collection
34
+ site = context.registers[:site]
35
+ member = find_member(site, member_id)
36
+ return '' unless member
37
+
38
+ # Return the requested property
39
+ case property
40
+ when 'name'
41
+ (member.data['member'] && member.data['member']['name']) || ''
42
+ when 'url'
43
+ site_url = site.config['url'] || ''
44
+ site_url + member.url
45
+ when 'path'
46
+ member.url
47
+ when 'image'
48
+ member_id_clean = member.id.gsub('/team/', '')
49
+ "/assets/images/team/#{member_id_clean}/profile.jpg"
50
+ when 'image-tag'
51
+ # Generate image path
52
+ member_id_clean = member.id.gsub('/team/', '')
53
+ image_path = "/assets/images/team/#{member_id_clean}/profile.jpg"
54
+
55
+ # Parse additional options for the image tag
56
+ image_options = parse_image_options(args[2..-1])
57
+
58
+ # Set default alt text if not provided
59
+ if !image_options['alt'] && member.data['member'] && member.data['member']['name']
60
+ image_options['alt'] = member.data['member']['name']
61
+ end
62
+
63
+ # Build the markup string for uj_image tag
64
+ image_markup = build_image_markup(image_path, image_options)
65
+
66
+ # Parse and render the uj_image tag using Liquid template
67
+ template_content = "{% uj_image #{image_markup} %}"
68
+ template = Liquid::Template.parse(template_content)
69
+ template.render!(context)
70
+ else
71
+ # Try to access any other property dynamically
72
+ (member.data['member'] && member.data['member'][property]) || member.data[property] || ''
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def parse_arguments_with_quotes(markup)
79
+ # Parse arguments preserving quotes for detection
80
+ args = []
81
+ current_arg = ''
82
+ in_quotes = false
83
+ quote_char = nil
84
+
85
+ markup.each_char.with_index do |char, i|
86
+ if !in_quotes && (char == '"' || char == "'")
87
+ in_quotes = true
88
+ quote_char = char
89
+ current_arg += char
90
+ elsif in_quotes && char == quote_char
91
+ in_quotes = false
92
+ current_arg += char
93
+ quote_char = nil
94
+ elsif !in_quotes && char == ','
95
+ args << current_arg.strip
96
+ current_arg = ''
97
+ else
98
+ current_arg += char
99
+ end
100
+ end
101
+
102
+ args << current_arg.strip if current_arg.strip.length > 0
103
+ args
104
+ end
105
+
106
+ def parse_arguments(markup)
107
+ # Parse arguments that can be quoted or unquoted
108
+ args = []
109
+ current_arg = ''
110
+ in_quotes = false
111
+ quote_char = nil
112
+
113
+ markup.each_char.with_index do |char, i|
114
+ if !in_quotes && (char == '"' || char == "'")
115
+ in_quotes = true
116
+ quote_char = char
117
+ elsif in_quotes && char == quote_char
118
+ in_quotes = false
119
+ quote_char = nil
120
+ elsif !in_quotes && char == ','
121
+ args << current_arg.strip
122
+ current_arg = ''
123
+ else
124
+ current_arg += char
125
+ end
126
+ end
127
+
128
+ args << current_arg.strip if current_arg.strip.length > 0
129
+ args
130
+ end
131
+
132
+ def resolve_member_id(context, member_input)
133
+ if member_input.nil? || member_input.empty?
134
+ # No input, try default sources
135
+ page = context['page']
136
+ return nil unless page
137
+
138
+ if page['post'] && page['post']['member']
139
+ page['post']['member']
140
+ elsif page['member'] && page['member']['name']
141
+ page['id']
142
+ else
143
+ nil
144
+ end
145
+ else
146
+ # Resolve the variable
147
+ resolve_variable(context, member_input)
148
+ end
149
+ end
150
+
151
+ def resolve_variable(context, variable_name)
152
+ # Handle nested variable access
153
+ parts = variable_name.split('.')
154
+ current = context
155
+
156
+ parts.each do |part|
157
+ return nil unless current.respond_to?(:[]) || current.is_a?(Hash)
158
+ current = current[part]
159
+ return nil if current.nil?
160
+ end
161
+
162
+ current
163
+ end
164
+
165
+ def find_member(site, member_id)
166
+ return nil unless site.collections['team']
167
+
168
+ site.collections['team'].docs.find do |member|
169
+ member.id.include?(member_id.to_s)
170
+ end
171
+ end
172
+
173
+ def parse_image_options(option_args)
174
+ options = {}
175
+
176
+ option_args.each do |arg|
177
+ # Strip quotes if present
178
+ arg_clean = arg.gsub(/^['"]|['"]$/, '')
179
+
180
+ if arg_clean.include?('=')
181
+ key, value = arg_clean.split('=', 2)
182
+ key = key.strip
183
+ value = value.strip.gsub(/^['"]|['"]$/, '')
184
+ options[key] = value
185
+ end
186
+ end
187
+
188
+ options
189
+ end
190
+
191
+ def build_image_markup(image_path, options)
192
+ # Build markup string in the format expected by uj_image tag
193
+ markup_parts = ["\"#{image_path}\""]
194
+
195
+ options.each do |key, value|
196
+ markup_parts << "#{key}=\"#{value}\""
197
+ end
198
+
199
+ markup_parts.join(', ')
200
+ end
201
+ end
202
+ end
203
+
204
+ Liquid::Template.register_tag('uj_member', Jekyll::UJMemberTag)
data/lib/tags/post.rb ADDED
@@ -0,0 +1,258 @@
1
+ # Libraries
2
+ require "jekyll"
3
+
4
+ module Jekyll
5
+ class UJPostTag < Liquid::Tag
6
+ def initialize(tag_name, markup, tokens)
7
+ super
8
+ @markup = markup.strip
9
+ end
10
+
11
+ def render(context)
12
+ # Parse arguments
13
+ args = parse_arguments_with_quotes(@markup)
14
+ post_input = args[0]
15
+ property_input = args[1] || "'title'" # Default to title if no property specified
16
+
17
+ # Strip quotes from property if present
18
+ property = property_input.gsub(/^['"]|['"]$/, '')
19
+
20
+ # Check if the post input was originally quoted
21
+ is_quoted = post_input && post_input.match(/^['"]/)
22
+
23
+ # Resolve post ID
24
+ if is_quoted
25
+ # If quoted, strip quotes and use as literal
26
+ post_id = post_input.gsub(/^['"]|['"]$/, '')
27
+ else
28
+ # Otherwise resolve as variable
29
+ post_id = resolve_post_id(context, post_input)
30
+ end
31
+ return '' unless post_id
32
+
33
+ # Find post in site collections
34
+ site = context.registers[:site]
35
+ post = find_post(site, post_id)
36
+ return '' unless post
37
+
38
+ # Return the requested property
39
+ case property
40
+ when 'title'
41
+ post.data['title'] || ''
42
+ when 'description'
43
+ post.data['description'] || post.data['excerpt'] || ''
44
+ when 'url'
45
+ site_url = site.config['url'] || ''
46
+ site_url + post.url
47
+ when 'path'
48
+ post.url
49
+ when 'image'
50
+ # Use the custom post.post.id if available, otherwise fall back to extracting from post.id
51
+ custom_id = (post.data['post'] && post.data['post']['id']) || post.id.gsub(/^\/(\w+)\//, '')
52
+ # Extract the slug from the Jekyll post ID
53
+ post_id_clean = post.id.gsub(/^\/(\w+)\//, '')
54
+ slug = post_id_clean.gsub(/^\d{4}-\d{2}-\d{2}-/, '')
55
+ "/assets/images/blog/post-#{custom_id}/#{slug}.jpg"
56
+ when 'date'
57
+ post.data['date'] ? post.data['date'].strftime('%Y-%m-%d') : ''
58
+ when 'author'
59
+ (post.data['post'] && post.data['post']['author']) || post.data['author'] || ''
60
+ when 'category'
61
+ post.data['category'] || post.data['categories']&.first || ''
62
+ when 'categories'
63
+ Array(post.data['categories']).join(', ')
64
+ when 'tags'
65
+ Array(post.data['tags']).join(', ')
66
+ when 'id'
67
+ post.id
68
+ when 'image-tag'
69
+ # Generate image path
70
+ # Use the custom post.post.id if available, otherwise fall back to extracting from post.id
71
+ custom_id = (post.data['post'] && post.data['post']['id']) || post.id.gsub(/^\/(\w+)\//, '')
72
+ # Extract the slug from the Jekyll post ID
73
+ post_id_clean = post.id.gsub(/^\/(\w+)\//, '')
74
+ slug = post_id_clean.gsub(/^\d{4}-\d{2}-\d{2}-/, '')
75
+ image_path = "/assets/images/blog/post-#{custom_id}/#{slug}.jpg"
76
+
77
+ # Parse additional options for the image tag
78
+ image_options = parse_image_options(args[2..-1], context)
79
+
80
+ # Set default alt text if not provided
81
+ if !image_options['alt']
82
+ # Try to get the title from post.post.title first, then fall back to post.title
83
+ default_alt = (post.data['post'] && post.data['post']['title']) || post.data['title']
84
+ image_options['alt'] = default_alt if default_alt
85
+ end
86
+
87
+ # Build the markup string for uj_image tag
88
+ image_markup = build_image_markup(image_path, image_options)
89
+
90
+ # Parse and render the uj_image tag using Liquid template
91
+ template_content = "{% uj_image #{image_markup} %}"
92
+ template = Liquid::Template.parse(template_content)
93
+ template.render!(context)
94
+ else
95
+ # Try to access any other property dynamically
96
+ (post.data['post'] && post.data['post'][property]) || post.data[property] || ''
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def parse_arguments_with_quotes(markup)
103
+ # Parse arguments preserving quotes for detection
104
+ args = []
105
+ current_arg = ''
106
+ in_quotes = false
107
+ quote_char = nil
108
+
109
+ markup.each_char.with_index do |char, i|
110
+ if !in_quotes && (char == '"' || char == "'")
111
+ in_quotes = true
112
+ quote_char = char
113
+ current_arg += char
114
+ elsif in_quotes && char == quote_char
115
+ in_quotes = false
116
+ current_arg += char
117
+ quote_char = nil
118
+ elsif !in_quotes && char == ','
119
+ args << current_arg.strip
120
+ current_arg = ''
121
+ else
122
+ current_arg += char
123
+ end
124
+ end
125
+
126
+ args << current_arg.strip if current_arg.strip.length > 0
127
+ args
128
+ end
129
+
130
+ def parse_arguments(markup)
131
+ # Parse arguments that can be quoted or unquoted
132
+ args = []
133
+ current_arg = ''
134
+ in_quotes = false
135
+ quote_char = nil
136
+
137
+ markup.each_char.with_index do |char, i|
138
+ if !in_quotes && (char == '"' || char == "'")
139
+ in_quotes = true
140
+ quote_char = char
141
+ elsif in_quotes && char == quote_char
142
+ in_quotes = false
143
+ quote_char = nil
144
+ elsif !in_quotes && char == ','
145
+ args << current_arg.strip
146
+ current_arg = ''
147
+ else
148
+ current_arg += char
149
+ end
150
+ end
151
+
152
+ args << current_arg.strip if current_arg.strip.length > 0
153
+ args
154
+ end
155
+
156
+ def resolve_post_id(context, post_input)
157
+ if post_input.nil? || post_input.empty?
158
+ # No input, use current page if it's a post
159
+ page = context['page']
160
+ return nil unless page
161
+
162
+ # Check if current page is a post
163
+ if page['post'] || page['collection'] == 'posts'
164
+ page['id']
165
+ else
166
+ nil
167
+ end
168
+ else
169
+ # Resolve the variable
170
+ resolve_variable(context, post_input)
171
+ end
172
+ end
173
+
174
+ def resolve_variable(context, variable_name)
175
+ # Handle nested variable access
176
+ parts = variable_name.split('.')
177
+ current = context
178
+
179
+ parts.each do |part|
180
+ return nil unless current.respond_to?(:[]) || current.is_a?(Hash)
181
+ current = current[part]
182
+ return nil if current.nil?
183
+ end
184
+
185
+ current
186
+ end
187
+
188
+ def find_post(site, post_id)
189
+ post_id_clean = post_id.to_s.strip
190
+
191
+ # Search in posts collection first
192
+ if site.collections['posts']
193
+ post = site.collections['posts'].docs.find do |doc|
194
+ # Check standard ID match
195
+ doc.id == post_id_clean ||
196
+ doc.id.include?(post_id_clean) ||
197
+ # Also check if the post has a custom post.id field that matches
198
+ (doc.data['post'] && doc.data['post']['id'] == post_id_clean)
199
+ end
200
+ return post if post
201
+ end
202
+
203
+ # Search in other collections that might contain posts
204
+ site.collections.each do |name, collection|
205
+ next if name == 'posts' # Already checked
206
+
207
+ post = collection.docs.find do |doc|
208
+ (doc.id == post_id_clean ||
209
+ doc.id.include?(post_id_clean) ||
210
+ # Check custom post.id field
211
+ (doc.data['post'] && doc.data['post']['id'] == post_id_clean)) &&
212
+ doc.data['post']
213
+ end
214
+ return post if post
215
+ end
216
+
217
+ nil
218
+ end
219
+
220
+ def parse_image_options(option_args, context)
221
+ options = {}
222
+
223
+ option_args.each do |arg|
224
+ if arg.include?('=')
225
+ key, value = arg.split('=', 2)
226
+ key = key.strip
227
+
228
+ # Check if the value is quoted (literal) or unquoted (variable)
229
+ if value.strip.match(/^['"].*['"]$/)
230
+ # It's a literal string, strip quotes
231
+ value = value.strip.gsub(/^['"]|['"]$/, '')
232
+ else
233
+ # It's a variable, resolve it
234
+ resolved_value = resolve_variable(context, value.strip)
235
+ value = resolved_value || value.strip
236
+ end
237
+
238
+ options[key] = value
239
+ end
240
+ end
241
+
242
+ options
243
+ end
244
+
245
+ def build_image_markup(image_path, options)
246
+ # Build markup string in the format expected by uj_image tag
247
+ markup_parts = ["\"#{image_path}\""]
248
+
249
+ options.each do |key, value|
250
+ markup_parts << "#{key}=\"#{value}\""
251
+ end
252
+
253
+ markup_parts.join(', ')
254
+ end
255
+ end
256
+ end
257
+
258
+ Liquid::Template.register_tag('uj_post', Jekyll::UJPostTag)
@@ -0,0 +1,73 @@
1
+ # Libraries
2
+ require "jekyll"
3
+
4
+ module Jekyll
5
+ class UJReadtimeTag < 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 '1' 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
+ # Calculate readtime (200 words per minute, minimum 1 minute)
23
+ readtime = (words / 200.0).ceil
24
+ readtime = 1 if readtime < 1
25
+
26
+ readtime.to_s
27
+ end
28
+
29
+ private
30
+
31
+ def resolve_content(context)
32
+ if @markup.empty?
33
+ # No argument, use page content
34
+ page = context['page']
35
+ return nil unless page
36
+ page['content']
37
+ else
38
+ # Resolve the variable name
39
+ resolve_variable(context, @markup)
40
+ end
41
+ end
42
+
43
+ def resolve_variable(context, variable_name)
44
+ # Handle nested variable access like page.content or include.content
45
+ parts = variable_name.split('.')
46
+ current = context
47
+
48
+ parts.each do |part|
49
+ return nil unless current.respond_to?(:[]) || current.is_a?(Hash)
50
+ current = current[part]
51
+ return nil if current.nil?
52
+ end
53
+
54
+ current
55
+ end
56
+
57
+ def strip_html(content)
58
+ # Remove HTML tags
59
+ content = content.to_s.gsub(/<script.*?<\/script>/m, '')
60
+ content = content.gsub(/<style.*?<\/style>/m, '')
61
+ content = content.gsub(/<[^>]+>/, ' ')
62
+ content = content.gsub(/\s+/, ' ')
63
+ content.strip
64
+ end
65
+
66
+ def count_words(text)
67
+ # Count words (split by whitespace)
68
+ text.split(/\s+/).length
69
+ end
70
+ end
71
+ end
72
+
73
+ Liquid::Template.register_tag('uj_readtime', Jekyll::UJReadtimeTag)
@@ -0,0 +1,84 @@
1
+ # Libraries
2
+ require "jekyll"
3
+
4
+ module Jekyll
5
+ class UJSocialTag < Liquid::Tag
6
+ # Social platform URL patterns
7
+ SOCIAL_URLS = {
8
+ 'facebook' => 'https://facebook.com/%s',
9
+ 'twitter' => 'https://twitter.com/%s',
10
+ 'linkedin' => 'https://linkedin.com/in/%s',
11
+ 'youtube' => 'https://youtube.com/@%s',
12
+ 'instagram' => 'https://instagram.com/%s',
13
+ 'tumblr' => 'https://%s.tumblr.com',
14
+ 'slack' => 'https://%s.slack.com',
15
+ 'discord' => 'https://discord.gg/%s',
16
+ 'github' => 'https://github.com/%s',
17
+ 'dev' => 'https://dev.to/%s',
18
+ 'tiktok' => 'https://tiktok.com/@%s',
19
+ 'twitch' => 'https://twitch.tv/%s',
20
+ 'soundcloud' => 'https://soundcloud.com/%s',
21
+ 'spotify' => 'https://open.spotify.com/user/%s',
22
+ 'mixcloud' => 'https://mixcloud.com/%s'
23
+ }
24
+
25
+ def initialize(tag_name, markup, tokens)
26
+ super
27
+ @markup = markup.strip
28
+ end
29
+
30
+ def render(context)
31
+ # Parse the platform name (can be quoted or unquoted)
32
+ platform_input = parse_argument(@markup)
33
+
34
+ # Resolve the platform name (could be a variable or literal string)
35
+ platform = resolve_variable(context, platform_input)
36
+
37
+ # If it didn't resolve to anything, use the input as a literal string
38
+ platform = platform_input if platform.nil? || platform.empty?
39
+
40
+ # Get the social handle from page.resolved.socials.{platform}
41
+ page = context['page']
42
+ return '' unless page
43
+
44
+ social_handle = page['resolved'] && page['resolved']['socials'] && page['resolved']['socials'][platform]
45
+ return '' unless social_handle && !social_handle.empty?
46
+
47
+ # Get the URL pattern for this platform
48
+ url_pattern = SOCIAL_URLS[platform]
49
+ return '' unless url_pattern
50
+
51
+ # Build the URL
52
+ url_pattern % social_handle
53
+ end
54
+
55
+ private
56
+
57
+ def parse_argument(markup)
58
+ # Remove quotes if present
59
+ cleaned = markup.strip
60
+ if (cleaned.start_with?('"') && cleaned.end_with?('"')) ||
61
+ (cleaned.start_with?("'") && cleaned.end_with?("'"))
62
+ cleaned[1..-2]
63
+ else
64
+ cleaned
65
+ end
66
+ end
67
+
68
+ def resolve_variable(context, variable_name)
69
+ # Handle nested variable access like page.social
70
+ parts = variable_name.split('.')
71
+ current = context
72
+
73
+ parts.each do |part|
74
+ return nil unless current.respond_to?(:[]) || current.is_a?(Hash)
75
+ current = current[part]
76
+ return nil if current.nil?
77
+ end
78
+
79
+ current
80
+ end
81
+ end
82
+ end
83
+
84
+ Liquid::Template.register_tag('uj_social', Jekyll::UJSocialTag)