jekyll-webawesome 0.6.1 → 0.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/CHANGELOG.md +37 -0
- data/README.md +0 -15
- data/jekyll-webawesome.gemspec +1 -1
- data/lib/jekyll/webawesome/code_block_transformer.rb +46 -43
- data/lib/jekyll/webawesome/plugin.rb +49 -13
- data/lib/jekyll/webawesome/version.rb +1 -1
- data/lib/jekyll/webawesome.rb +13 -1
- metadata +4 -20
- data/lib/jekyll/webawesome/transformer.rb +0 -31
- data/lib/jekyll/webawesome/transformers/badge_transformer.rb +0 -48
- data/lib/jekyll/webawesome/transformers/base_transformer.rb +0 -65
- data/lib/jekyll/webawesome/transformers/button_transformer.rb +0 -68
- data/lib/jekyll/webawesome/transformers/callout_transformer.rb +0 -72
- data/lib/jekyll/webawesome/transformers/card_transformer.rb +0 -121
- data/lib/jekyll/webawesome/transformers/carousel_transformer.rb +0 -129
- data/lib/jekyll/webawesome/transformers/comparison_transformer.rb +0 -74
- data/lib/jekyll/webawesome/transformers/copy_button_transformer.rb +0 -49
- data/lib/jekyll/webawesome/transformers/details_transformer.rb +0 -85
- data/lib/jekyll/webawesome/transformers/dialog_transformer.rb +0 -175
- data/lib/jekyll/webawesome/transformers/icon_transformer.rb +0 -82
- data/lib/jekyll/webawesome/transformers/image_dialog_transformer.rb +0 -174
- data/lib/jekyll/webawesome/transformers/tabs_transformer.rb +0 -53
- data/lib/jekyll/webawesome/transformers/tag_transformer.rb +0 -58
- data/lib/jekyll/webawesome/transformers.rb +0 -19
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base_transformer'
|
|
4
|
-
|
|
5
|
-
module Jekyll
|
|
6
|
-
module WebAwesome
|
|
7
|
-
# Transforms card syntax into wa-card elements
|
|
8
|
-
# Primary syntax: ===appearance?\ncontent\n===
|
|
9
|
-
# Alternative syntax: :::wa-card appearance?\ncontent\n:::
|
|
10
|
-
# Appearances: outlined (default), filled, filled-outlined, plain, accent
|
|
11
|
-
class CardTransformer < BaseTransformer
|
|
12
|
-
def self.transform(content)
|
|
13
|
-
# Define both regex patterns
|
|
14
|
-
primary_regex = /^===(outlined|filled|filled-outlined|plain|accent)?\n(.*?)\n===/m
|
|
15
|
-
alternative_regex = /^:::wa-card\s*(outlined|filled|filled-outlined|plain|accent)?\n(.*?)\n:::/m
|
|
16
|
-
|
|
17
|
-
# Define shared transformation logic
|
|
18
|
-
transform_proc = proc do |appearance_param, card_content|
|
|
19
|
-
card_content = card_content.strip
|
|
20
|
-
|
|
21
|
-
appearance = normalize_appearance(appearance_param)
|
|
22
|
-
card_parts = parse_card_content(card_content)
|
|
23
|
-
|
|
24
|
-
build_card_html(card_parts, appearance)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Apply both patterns
|
|
28
|
-
patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
|
|
29
|
-
apply_multiple_patterns(content, patterns)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
class << self
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def normalize_appearance(appearance_param)
|
|
36
|
-
case appearance_param
|
|
37
|
-
when 'filled', 'filled-outlined', 'plain', 'accent'
|
|
38
|
-
appearance_param
|
|
39
|
-
else
|
|
40
|
-
'outlined' # default
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def parse_card_content(content)
|
|
45
|
-
parts = {
|
|
46
|
-
media: nil,
|
|
47
|
-
header: nil,
|
|
48
|
-
content: content,
|
|
49
|
-
footer: nil
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# Extract first image as media
|
|
53
|
-
if content.match(/^!\[([^\]]*)\]\(([^)]+)\)/)
|
|
54
|
-
parts[:media] = {
|
|
55
|
-
alt: ::Regexp.last_match(1),
|
|
56
|
-
src: ::Regexp.last_match(2)
|
|
57
|
-
}
|
|
58
|
-
# Remove the image from content
|
|
59
|
-
content = content.sub(/^!\[([^\]]*)\]\(([^)]+)\)\n?/, '')
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Extract first heading as header
|
|
63
|
-
if content.match(/^# (.+)$/)
|
|
64
|
-
parts[:header] = ::Regexp.last_match(1).strip
|
|
65
|
-
# Remove the heading from content
|
|
66
|
-
content = content.sub(/^# .+\n?/, '')
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Extract trailing buttons/links as footer
|
|
70
|
-
# Look for links or buttons at the end of the content
|
|
71
|
-
if content.match(/\n\[([^\]]+)\]\(([^)]+)\)\s*$/)
|
|
72
|
-
parts[:footer] = {
|
|
73
|
-
text: ::Regexp.last_match(1),
|
|
74
|
-
href: ::Regexp.last_match(2)
|
|
75
|
-
}
|
|
76
|
-
# Remove the footer link from content
|
|
77
|
-
content = content.sub(/\n\[([^\]]+)\]\(([^)]+)\)\s*$/, '')
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Update the main content after extractions
|
|
81
|
-
parts[:content] = content.strip
|
|
82
|
-
parts
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def build_card_html(parts, appearance)
|
|
86
|
-
attributes = []
|
|
87
|
-
attributes << "appearance=\"#{appearance}\"" if appearance != 'outlined'
|
|
88
|
-
|
|
89
|
-
# Add SSR attributes if slots are present
|
|
90
|
-
attributes << 'with-media' if parts[:media]
|
|
91
|
-
attributes << 'with-header' if parts[:header]
|
|
92
|
-
attributes << 'with-footer' if parts[:footer]
|
|
93
|
-
|
|
94
|
-
attr_string = attributes.empty? ? '' : " #{attributes.join(' ')}"
|
|
95
|
-
|
|
96
|
-
html_parts = []
|
|
97
|
-
|
|
98
|
-
# Media slot
|
|
99
|
-
html_parts << "<img slot=\"media\" src=\"#{parts[:media][:src]}\" alt=\"#{parts[:media][:alt]}\">" if parts[:media]
|
|
100
|
-
|
|
101
|
-
# Header slot
|
|
102
|
-
if parts[:header]
|
|
103
|
-
header_html = markdown_to_html(parts[:header])
|
|
104
|
-
html_parts << "<div slot=\"header\">#{header_html}</div>"
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Main content
|
|
108
|
-
if parts[:content] && !parts[:content].empty?
|
|
109
|
-
content_html = markdown_to_html(parts[:content])
|
|
110
|
-
html_parts << content_html
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Footer slot
|
|
114
|
-
html_parts << "<div slot=\"footer\"><wa-button href=\"#{parts[:footer][:href]}\">#{parts[:footer][:text]}</wa-button></div>" if parts[:footer]
|
|
115
|
-
|
|
116
|
-
"<wa-card#{attr_string}>#{html_parts.join}</wa-card>"
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base_transformer'
|
|
4
|
-
|
|
5
|
-
module Jekyll
|
|
6
|
-
module WebAwesome
|
|
7
|
-
# Transforms carousel syntax into wa-carousel elements
|
|
8
|
-
# Primary syntax: ~~~~~~params\n~~~ slide1\ncontent\n~~~\n~~~ slide2\ncontent\n~~~\n~~~~~~
|
|
9
|
-
# Alternative syntax: :::wa-carousel params\n~~~ slide1\ncontent\n~~~\n~~~ slide2\ncontent\n~~~\n:::
|
|
10
|
-
# Params can include: numbers (slides-per-page, slides-per-move), keywords (loop, navigation, pagination,
|
|
11
|
-
# autoplay, mouse-dragging, vertical), and CSS properties (scroll-hint:value, aspect-ratio:value, slide-gap:value)
|
|
12
|
-
class CarouselTransformer < BaseTransformer
|
|
13
|
-
def self.transform(content)
|
|
14
|
-
# Define both regex patterns
|
|
15
|
-
# Match: ~~~~~~params (optional)
|
|
16
|
-
# ~~~
|
|
17
|
-
# content (can be empty)
|
|
18
|
-
# ~~~
|
|
19
|
-
# (repeat slides)
|
|
20
|
-
# ~~~~~~
|
|
21
|
-
primary_regex = /^~{6}([^\n]*)\n((?:~~~\n(?:.*?\n)?~~~\n?)+)~{6}/m
|
|
22
|
-
alternative_regex = /^:::wa-carousel\s*([^\n]*)\n((?:~~~\n(?:.*?\n)?~~~\n?)+):::/m
|
|
23
|
-
|
|
24
|
-
# Define shared transformation logic
|
|
25
|
-
transform_proc = proc do |params, slides_block, _third_capture|
|
|
26
|
-
parsed_params = parse_params(params)
|
|
27
|
-
slides = extract_slides(slides_block)
|
|
28
|
-
|
|
29
|
-
build_carousel_html(slides, parsed_params)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Apply both patterns
|
|
33
|
-
patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
|
|
34
|
-
apply_multiple_patterns(content, patterns)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
class << self
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
def parse_params(params)
|
|
41
|
-
return {} if params.nil? || params.strip.empty?
|
|
42
|
-
|
|
43
|
-
result = {
|
|
44
|
-
attributes: {},
|
|
45
|
-
css_vars: {}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
tokens = params.strip.split(/\s+/)
|
|
49
|
-
numeric_count = 0
|
|
50
|
-
|
|
51
|
-
tokens.each do |token|
|
|
52
|
-
# Check for CSS custom properties (key:value)
|
|
53
|
-
if token.include?(':')
|
|
54
|
-
key, value = token.split(':', 2)
|
|
55
|
-
case key
|
|
56
|
-
when 'scroll-hint'
|
|
57
|
-
result[:css_vars]['--scroll-hint'] = value
|
|
58
|
-
when 'aspect-ratio'
|
|
59
|
-
# Support 'auto', 'none', or 'unset' to remove the default aspect ratio
|
|
60
|
-
# This is useful for text content or variable-height slides
|
|
61
|
-
result[:css_vars]['--aspect-ratio'] = value
|
|
62
|
-
when 'slide-gap'
|
|
63
|
-
result[:css_vars]['--slide-gap'] = value
|
|
64
|
-
end
|
|
65
|
-
# Check for numeric values
|
|
66
|
-
elsif token.match?(/^\d+$/)
|
|
67
|
-
numeric_count += 1
|
|
68
|
-
if numeric_count == 1
|
|
69
|
-
result[:attributes]['slides-per-page'] = token
|
|
70
|
-
elsif numeric_count == 2
|
|
71
|
-
result[:attributes]['slides-per-move'] = token
|
|
72
|
-
end
|
|
73
|
-
# Check for boolean flags
|
|
74
|
-
elsif %w[loop navigation pagination autoplay mouse-dragging vertical].include?(token)
|
|
75
|
-
# For orientation, we need to handle it specially
|
|
76
|
-
if token == 'vertical'
|
|
77
|
-
result[:attributes]['orientation'] = 'vertical'
|
|
78
|
-
else
|
|
79
|
-
result[:attributes][token] = true
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
result
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def extract_slides(slides_block)
|
|
88
|
-
# Extract individual slides using ~~~ markers
|
|
89
|
-
# Handle both content and empty slides
|
|
90
|
-
slide_contents = slides_block.scan(/~~~\n(.*?)~~~(?:\n|$)/m)
|
|
91
|
-
slide_contents.map { |match| match[0].strip }
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def build_carousel_html(slides, parsed_params)
|
|
95
|
-
attributes = build_attributes(parsed_params[:attributes] || {})
|
|
96
|
-
style = build_style(parsed_params[:css_vars] || {})
|
|
97
|
-
|
|
98
|
-
attr_string = attributes.empty? ? '' : " #{attributes.join(' ')}"
|
|
99
|
-
style_string = style.empty? ? '' : " style=\"#{style}\""
|
|
100
|
-
|
|
101
|
-
slide_items = slides.map do |slide_content|
|
|
102
|
-
slide_html = markdown_to_html(slide_content)
|
|
103
|
-
"<wa-carousel-item>#{slide_html}</wa-carousel-item>"
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
"<wa-carousel#{attr_string}#{style_string}>#{slide_items.join}</wa-carousel>"
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def build_attributes(attrs)
|
|
110
|
-
return [] if attrs.nil? || attrs.empty?
|
|
111
|
-
|
|
112
|
-
attrs.map do |key, value|
|
|
113
|
-
if value == true
|
|
114
|
-
key
|
|
115
|
-
else
|
|
116
|
-
"#{key}=\"#{value}\""
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def build_style(css_vars)
|
|
122
|
-
return '' if css_vars.nil? || css_vars.empty?
|
|
123
|
-
|
|
124
|
-
css_vars.map { |key, value| "#{key}: #{value}" }.join('; ')
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base_transformer'
|
|
4
|
-
|
|
5
|
-
module Jekyll
|
|
6
|
-
module WebAwesome
|
|
7
|
-
# Transforms comparison syntax into wa-comparison elements
|
|
8
|
-
# Primary syntax: |||\n\n\n|||
|
|
9
|
-
# Primary syntax with position: |||50\n\n\n|||
|
|
10
|
-
# Alternative syntax: :::wa-comparison\n\n\n:::
|
|
11
|
-
# Alternative syntax with position: :::wa-comparison 50\n\n\n:::
|
|
12
|
-
# Expects exactly two image elements inside the wrapper
|
|
13
|
-
class ComparisonTransformer < BaseTransformer
|
|
14
|
-
def self.transform(content)
|
|
15
|
-
# Process primary syntax
|
|
16
|
-
content = content.gsub(/^\|\|\|(\d+)?\n(.*?)\n\|\|\|/m) do |match|
|
|
17
|
-
position = Regexp.last_match(1)
|
|
18
|
-
inner_content = Regexp.last_match(2).strip
|
|
19
|
-
images = extract_images(inner_content)
|
|
20
|
-
|
|
21
|
-
if images.length == 2
|
|
22
|
-
build_comparison_html(inner_content, position)
|
|
23
|
-
else
|
|
24
|
-
match # Return original match if not exactly 2 images
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Process alternative syntax
|
|
29
|
-
content.gsub(/^:::wa-comparison\s*(\d+)?\n(.*?)\n:::/m) do |match|
|
|
30
|
-
position = Regexp.last_match(1)
|
|
31
|
-
inner_content = Regexp.last_match(2).strip
|
|
32
|
-
images = extract_images(inner_content)
|
|
33
|
-
|
|
34
|
-
if images.length == 2
|
|
35
|
-
build_comparison_html(inner_content, position)
|
|
36
|
-
else
|
|
37
|
-
match # Return original match if not exactly 2 images
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
class << self
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def build_comparison_html(content, position = nil)
|
|
46
|
-
images = extract_images(content)
|
|
47
|
-
|
|
48
|
-
before_image = build_image_html(images[0], 'before')
|
|
49
|
-
after_image = build_image_html(images[1], 'after')
|
|
50
|
-
|
|
51
|
-
position_attr = position ? " position=\"#{position}\"" : ''
|
|
52
|
-
|
|
53
|
-
"<wa-comparison#{position_attr}>#{before_image}#{after_image}</wa-comparison>"
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def extract_images(content)
|
|
57
|
-
# Extract markdown image syntax: 
|
|
58
|
-
image_regex = /!\[([^\]]*)\]\(([^)]+)\)/
|
|
59
|
-
content.scan(image_regex)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def build_image_html(image_match, slot)
|
|
63
|
-
alt_text = image_match[0]
|
|
64
|
-
src = image_match[1]
|
|
65
|
-
|
|
66
|
-
# Escape HTML characters in alt text
|
|
67
|
-
escaped_alt = alt_text.gsub('&', '&').gsub('"', '"').gsub('<', '<').gsub('>', '>')
|
|
68
|
-
|
|
69
|
-
"<img slot=\"#{slot}\" src=\"#{src}\" alt=\"#{escaped_alt}\" />"
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base_transformer'
|
|
4
|
-
|
|
5
|
-
module Jekyll
|
|
6
|
-
module WebAwesome
|
|
7
|
-
# Transforms copy button syntax into wa-copy-button elements
|
|
8
|
-
# Primary syntax: <<<\ncontent\n<<<
|
|
9
|
-
# Alternative syntax: :::wa-copy-button\ncontent\n:::
|
|
10
|
-
#
|
|
11
|
-
# Usage:
|
|
12
|
-
# <<<
|
|
13
|
-
# This text will be copied to clipboard
|
|
14
|
-
# <<<
|
|
15
|
-
#
|
|
16
|
-
# :::wa-copy-button
|
|
17
|
-
# Copy this text
|
|
18
|
-
# :::
|
|
19
|
-
class CopyButtonTransformer < BaseTransformer
|
|
20
|
-
def self.transform(content)
|
|
21
|
-
# Define both regex patterns
|
|
22
|
-
primary_regex = /^<<<\n(.*?)\n<<</m
|
|
23
|
-
alternative_regex = /^:::wa-copy-button\n(.*?)\n:::/m
|
|
24
|
-
|
|
25
|
-
# Define shared transformation logic
|
|
26
|
-
transform_proc = proc do |copy_content|
|
|
27
|
-
copy_content = copy_content.strip
|
|
28
|
-
|
|
29
|
-
build_copy_button_html(copy_content)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Apply both patterns
|
|
33
|
-
patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
|
|
34
|
-
apply_multiple_patterns(content, patterns)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
class << self
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
def build_copy_button_html(content)
|
|
41
|
-
# Escape the content for the value attribute
|
|
42
|
-
escaped_content = content.gsub('"', '"').gsub("'", ''')
|
|
43
|
-
|
|
44
|
-
"<wa-copy-button value=\"#{escaped_content}\"></wa-copy-button>"
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base_transformer'
|
|
4
|
-
|
|
5
|
-
module Jekyll
|
|
6
|
-
module WebAwesome
|
|
7
|
-
# Transforms summary/details syntax into wa-details elements
|
|
8
|
-
# Primary syntax: ^^^appearance? icon-placement?\nsummary\n>>>\ndetails\n^^^
|
|
9
|
-
# Alternative syntax: :::wa-details appearance? icon-placement?\nsummary\n>>>\ndetails\n:::
|
|
10
|
-
# Appearances: outlined (default), filled, filled-outlined, plain
|
|
11
|
-
# Icon placement: start, end (default)
|
|
12
|
-
class DetailsTransformer < BaseTransformer
|
|
13
|
-
def self.transform(content)
|
|
14
|
-
# Define both regex patterns - capture parameter string
|
|
15
|
-
primary_regex = /^\^\^\^?(.*?)\n(.*?)\n^>>>\n(.*?)\n^\^\^\^?/m
|
|
16
|
-
alternative_regex = /^:::wa-details\s*(.*?)\n(.*?)\n^>>>\n(.*?)\n:::/m
|
|
17
|
-
|
|
18
|
-
# Define shared transformation logic
|
|
19
|
-
transform_proc = proc do |params_string, summary_content, details_content|
|
|
20
|
-
summary_content = summary_content.strip
|
|
21
|
-
details_content = details_content.strip
|
|
22
|
-
|
|
23
|
-
# Parse parameters from the params string
|
|
24
|
-
appearance_param, icon_placement_param = parse_parameters(params_string)
|
|
25
|
-
|
|
26
|
-
appearance_class = normalize_appearance(appearance_param)
|
|
27
|
-
icon_placement = normalize_icon_placement(icon_placement_param)
|
|
28
|
-
summary_html = markdown_to_html(summary_content)
|
|
29
|
-
details_html = markdown_to_html(details_content)
|
|
30
|
-
|
|
31
|
-
"<wa-details appearance='#{appearance_class}' icon-placement='#{icon_placement}'>" \
|
|
32
|
-
"<span slot='summary'>#{summary_html}</span>" \
|
|
33
|
-
"#{details_html}</wa-details>"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Apply both patterns
|
|
37
|
-
patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
|
|
38
|
-
apply_multiple_patterns(content, patterns)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
class << self
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
def parse_parameters(params_string)
|
|
45
|
-
return [nil, nil] if params_string.nil? || params_string.strip.empty?
|
|
46
|
-
|
|
47
|
-
# Split by whitespace and extract known parameters
|
|
48
|
-
tokens = params_string.strip.split(/\s+/)
|
|
49
|
-
|
|
50
|
-
appearance_options = %w[outlined filled filled-outlined plain]
|
|
51
|
-
placement_options = %w[start end]
|
|
52
|
-
|
|
53
|
-
appearance_param = tokens.find { |token| appearance_options.include?(token) }
|
|
54
|
-
icon_placement_param = tokens.find { |token| placement_options.include?(token) }
|
|
55
|
-
|
|
56
|
-
[appearance_param, icon_placement_param]
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def normalize_appearance(appearance_param)
|
|
60
|
-
case appearance_param
|
|
61
|
-
when 'filled'
|
|
62
|
-
'filled'
|
|
63
|
-
when 'filled-outlined'
|
|
64
|
-
'filled outlined'
|
|
65
|
-
when 'plain'
|
|
66
|
-
'plain'
|
|
67
|
-
else
|
|
68
|
-
'outlined'
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def normalize_icon_placement(icon_placement_param)
|
|
73
|
-
case icon_placement_param
|
|
74
|
-
when 'start'
|
|
75
|
-
'start'
|
|
76
|
-
when 'end'
|
|
77
|
-
'end'
|
|
78
|
-
else
|
|
79
|
-
'end'
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'digest'
|
|
4
|
-
require_relative 'base_transformer'
|
|
5
|
-
|
|
6
|
-
module Jekyll
|
|
7
|
-
module WebAwesome
|
|
8
|
-
# Transforms dialog syntax into wa-dialog elements with trigger buttons
|
|
9
|
-
# Primary syntax: ???params\nbutton text\n>>>\ncontent\n???
|
|
10
|
-
# Alternative syntax: :::wa-dialog params\nbutton text\n>>>\ncontent\n:::
|
|
11
|
-
# Params: light-dismiss and optional width (e.g., 500px, 50vw, 40em)
|
|
12
|
-
# Note: Header with close X button is always enabled for accessibility
|
|
13
|
-
class DialogTransformer < BaseTransformer
|
|
14
|
-
def self.transform(content)
|
|
15
|
-
# Define both regex patterns - capture parameter string, button text, and content
|
|
16
|
-
# Params are on the same line as the opening delimiter
|
|
17
|
-
# Button text is on the next line(s) until >>>
|
|
18
|
-
# Content is everything after >>> until the closing delimiter
|
|
19
|
-
primary_regex = /^\?\?\?([^\n]*)$\n(.*?)\n^>>>$\n(.*?)\n^\?\?\?$/m
|
|
20
|
-
alternative_regex = /^:::wa-dialog([^\n]*)$\n(.*?)\n^>>>$\n(.*?)\n^:::$/m
|
|
21
|
-
|
|
22
|
-
# Define shared transformation logic
|
|
23
|
-
transform_proc = proc do |params_string, button_text, dialog_content|
|
|
24
|
-
button_text = button_text.strip
|
|
25
|
-
dialog_content = dialog_content.strip
|
|
26
|
-
|
|
27
|
-
# Parse parameters
|
|
28
|
-
light_dismiss, width = parse_parameters(params_string)
|
|
29
|
-
|
|
30
|
-
# Extract label from first heading or use button text
|
|
31
|
-
label, content_without_label = extract_label(dialog_content, button_text)
|
|
32
|
-
|
|
33
|
-
# Generate unique ID based on content
|
|
34
|
-
dialog_id = generate_dialog_id(button_text, dialog_content)
|
|
35
|
-
|
|
36
|
-
# Convert markdown to HTML
|
|
37
|
-
content_html = markdown_to_html(content_without_label)
|
|
38
|
-
|
|
39
|
-
# Build the dialog HTML
|
|
40
|
-
build_dialog_html(dialog_id, button_text, label, content_html,
|
|
41
|
-
light_dismiss, width)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Apply both patterns
|
|
45
|
-
patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
|
|
46
|
-
apply_multiple_patterns(content, patterns)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
class << self
|
|
50
|
-
private
|
|
51
|
-
|
|
52
|
-
# Parse parameters from the params string
|
|
53
|
-
def parse_parameters(params_string)
|
|
54
|
-
return [false, nil] if params_string.nil? || params_string.strip.empty?
|
|
55
|
-
|
|
56
|
-
tokens = params_string.strip.split(/\s+/)
|
|
57
|
-
|
|
58
|
-
light_dismiss = tokens.include?('light-dismiss')
|
|
59
|
-
|
|
60
|
-
# Look for width parameter (last token with CSS units)
|
|
61
|
-
width = nil
|
|
62
|
-
tokens.reverse_each do |token|
|
|
63
|
-
if token.match?(/^\d+(\.\d+)?(px|em|rem|vw|vh|%|ch)$/)
|
|
64
|
-
width = token
|
|
65
|
-
break
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
[light_dismiss, width]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Extract label from first heading in content
|
|
73
|
-
# Always returns a label - uses heading if available, otherwise default_label
|
|
74
|
-
def extract_label(content, default_label)
|
|
75
|
-
# Check if content starts with a heading
|
|
76
|
-
if content.match(/^#\s+(.+?)$/)
|
|
77
|
-
label = Regexp.last_match(1).strip
|
|
78
|
-
# Remove the heading from content
|
|
79
|
-
content_without_label = content.sub(/^#\s+.+?\n/, '').strip
|
|
80
|
-
[label, content_without_label]
|
|
81
|
-
else
|
|
82
|
-
# Use default label (button text) to ensure header is always shown
|
|
83
|
-
[default_label, content]
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Generate a unique ID for the dialog using MD5 hash
|
|
88
|
-
def generate_dialog_id(button_text, content)
|
|
89
|
-
hash_input = "#{button_text}#{content}"
|
|
90
|
-
hash = Digest::MD5.hexdigest(hash_input)
|
|
91
|
-
"dialog-#{hash[0..7]}" # Use first 8 characters of hash
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Build the complete dialog HTML with trigger button
|
|
95
|
-
# Header with X close button is always enabled for accessibility
|
|
96
|
-
def build_dialog_html(dialog_id, button_text, label, content_html,
|
|
97
|
-
light_dismiss, width)
|
|
98
|
-
# Build dialog attributes
|
|
99
|
-
dialog_attrs = ["id='#{dialog_id}'"]
|
|
100
|
-
# Escape both HTML and attribute characters for label
|
|
101
|
-
# Header is always shown to provide the X close button
|
|
102
|
-
dialog_attrs << "label='#{escape_attribute(escape_html(label))}'"
|
|
103
|
-
dialog_attrs << 'light-dismiss' if light_dismiss
|
|
104
|
-
|
|
105
|
-
# Build style attribute for width if specified
|
|
106
|
-
style_attr = width ? " style='--width: #{width}'" : ''
|
|
107
|
-
|
|
108
|
-
# Check if button contains an image (for image dialog support)
|
|
109
|
-
is_image_button = button_text.include?('<img')
|
|
110
|
-
|
|
111
|
-
# Build the HTML
|
|
112
|
-
html = []
|
|
113
|
-
|
|
114
|
-
# Add CSS Parts styling for image buttons to make them invisible
|
|
115
|
-
if is_image_button
|
|
116
|
-
button_id = "#{dialog_id}-btn"
|
|
117
|
-
html << '<style>'
|
|
118
|
-
html << " ##{button_id}::part(base) {"
|
|
119
|
-
html << ' padding: 0;'
|
|
120
|
-
html << ' margin: 0;'
|
|
121
|
-
html << ' border: none;'
|
|
122
|
-
html << ' background: transparent;'
|
|
123
|
-
html << ' box-shadow: none;'
|
|
124
|
-
html << ' color: inherit;'
|
|
125
|
-
html << ' min-width: 0;'
|
|
126
|
-
html << ' height: auto;'
|
|
127
|
-
html << ' }'
|
|
128
|
-
html << " ##{button_id}::part(base):hover {"
|
|
129
|
-
html << ' background: transparent;'
|
|
130
|
-
html << ' border-color: transparent;'
|
|
131
|
-
html << ' }'
|
|
132
|
-
html << " ##{button_id}::part(base):active {"
|
|
133
|
-
html << ' background: transparent;'
|
|
134
|
-
html << ' border-color: transparent;'
|
|
135
|
-
html << ' }'
|
|
136
|
-
html << '</style>'
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Trigger button
|
|
140
|
-
# Only allow HTML for image tags (for image dialog support), escape everything else for security
|
|
141
|
-
button_content = is_image_button ? button_text : escape_html(button_text)
|
|
142
|
-
button_id_attr = is_image_button ? " id='#{button_id}'" : ''
|
|
143
|
-
button_variant = is_image_button ? " variant='text'" : ''
|
|
144
|
-
html << "<wa-button#{button_id_attr}#{button_variant} data-dialog='open #{dialog_id}'>#{button_content}</wa-button>"
|
|
145
|
-
|
|
146
|
-
# Dialog element
|
|
147
|
-
html << "<wa-dialog #{dialog_attrs.join(' ')}#{style_attr}>"
|
|
148
|
-
html << content_html
|
|
149
|
-
|
|
150
|
-
# Footer with close button
|
|
151
|
-
html << "<wa-button slot='footer' variant='primary' data-dialog='close'>Close</wa-button>"
|
|
152
|
-
|
|
153
|
-
html << '</wa-dialog>'
|
|
154
|
-
|
|
155
|
-
html.join("\n")
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Escape HTML entities in text
|
|
159
|
-
def escape_html(text)
|
|
160
|
-
text.gsub('&', '&')
|
|
161
|
-
.gsub('<', '<')
|
|
162
|
-
.gsub('>', '>')
|
|
163
|
-
.gsub('"', '"')
|
|
164
|
-
.gsub("'", ''')
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Escape attribute values
|
|
168
|
-
def escape_attribute(text)
|
|
169
|
-
text.gsub("'", ''')
|
|
170
|
-
.gsub('"', '"')
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
end
|