jekyll-webawesome 0.4.0 → 0.5.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: 248a4b65617851cba2e7b5cc7b1a14f9424fa38649b0efbe7f5af4417b71df7f
4
- data.tar.gz: 157cfaef53cc8d38d1b7d3d0ddd6fc2c383a9a2989da6f5be9a62e9dc9d19bd0
3
+ metadata.gz: e958cd124d7cb081e95cc61a14db48b6c0059daa49fef318c452a3cab30fecfb
4
+ data.tar.gz: b531b0aa50c859837a7500fbdb45d0c46dc0c936163b139715592b60fc3b05c5
5
5
  SHA512:
6
- metadata.gz: fb931b98a57633b9924bbaf3143f4b3cda7bab15e3778f1d698775c95e349a3802d6f1d9fa537ebe8424e3e4debbf9d0d411101e2fd9481bbd9aee7224e86bc9
7
- data.tar.gz: b0d50534280129717c3fa47445a66cf44313995b31061728679ad3324c05e9927934686e1b9a9343f7037f62fde575581c25571ebe437c2f1ed70c90cd8ba803
6
+ metadata.gz: 042c0a994e97c359c06edf42103fd0308bcab11f9c241b43e402fdae0bb0176ce72ab30e371a0d521fda3149245187c92447e201e0073b219573d649260c187a
7
+ data.tar.gz: f44d6a009eee1d5f2b807bb2fdcc858d2372646a3900165f42d2c6c7dd0eaa7ad2b30d506d63f95ed3de9f47c130ea6a4b2f9560ff8ee7f2e95eea165508e70b
data/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
8
8
 
9
9
  - Placeholder
10
10
 
11
+ ## [0.5.0] - 2025-10-20
12
+
13
+ - Add support for wa-dialog component
14
+ - Image dialog auto-transformation feature
15
+ - New `image_dialog` configuration option (opt-in, default: false)
16
+ - Automatically transforms markdown images into clickable dialogs
17
+ - Support for custom dialog width via title attribute (e.g., `"50%"`, `"800px"`, `"90vw"`)
18
+ - Opt-out mechanism via `"nodialog"` in title attribute
19
+
20
+ ## [0.4.1] - 2025-10-02
21
+
22
+ - Code blocks inside custom elements (wa-details, wa-callout, etc.) are now properly protected from markdown processing
23
+ - Previously, code blocks would be corrupted with HTML entities and extra `<p>` tags when nested inside custom elements
24
+
11
25
  ## [0.4.0] - 2025-09-29
12
26
 
13
27
  - Support for icon placement in wa-details component
data/README.md CHANGED
@@ -19,6 +19,7 @@ This plugin focuses on the most commonly used Web Awesome components for Jekyll
19
19
  | **Comparison** | `\|\|\|` or `\|\|\|25` | `:::wa-comparison` or `:::wa-comparison 25` | `<wa-comparison>` with before/after slots |
20
20
  | **Copy Button** | `<<<` | `:::wa-copy-button` | `<wa-copy-button value="content">content</wa-copy-button>` |
21
21
  | **Details** | `^^^appearance? icon-placement?` | `:::wa-details appearance? icon-placement?` | `<wa-details appearance="..." icon-placement="...">content</wa-details>` |
22
+ | **Dialog** | `???params?` | `:::wa-dialog params?` | `<wa-dialog>` with trigger button and content |
22
23
  | **Tab Group** | `++++++` | `:::wa-tabs` | `<wa-tab-group><wa-tab>content</wa-tab></wa-tab-group>` |
23
24
  | **Tag** | `@@@brand` | `:::wa-tag brand` | `<wa-tag variant="brand">content</wa-tag>` |
24
25
 
@@ -66,6 +67,9 @@ webawesome:
66
67
  # Control which file types to transform (default: both true)
67
68
  transform_pages: true # Transform pages (like index.md, about.md)
68
69
  transform_documents: true # Transform documents (like blog posts in _posts)
70
+
71
+ # Enable automatic image-to-dialog transformation (default: false)
72
+ image_dialog: true # Makes all images clickable and open in dialogs
69
73
  ```
70
74
 
71
75
  And then execute:
@@ -332,6 +336,108 @@ Copy buttons work well for:
332
336
 
333
337
  > **Note**: The `value` attribute contains the raw text (including any markdown) that gets copied to the clipboard. The copy button displays the standard Web Awesome copy icon and handles the clipboard operation automatically.
334
338
 
339
+ ### Dialogs
340
+
341
+ Create interactive dialogs (modals) using the `???` syntax:
342
+
343
+ ```markdown
344
+ ???
345
+ Open Dialog
346
+ >>>
347
+ This is the dialog content with **markdown** support.
348
+ ???
349
+ ```
350
+
351
+ This creates a trigger button and a dialog that opens when clicked:
352
+
353
+ ```html
354
+ <wa-button data-dialog='open dialog-abc123'>Open Dialog</wa-button>
355
+ <wa-dialog id='dialog-abc123' label='Open Dialog'>
356
+ <p>This is the dialog content with <strong>markdown</strong> support.</p>
357
+ <wa-button slot='footer' variant='primary' data-dialog='close'>Close</wa-button>
358
+ </wa-dialog>
359
+ ```
360
+
361
+ #### Dialogs with Headings
362
+
363
+ The first heading (`#`) automatically becomes the dialog's label (title):
364
+
365
+ ```markdown
366
+ ???
367
+ Show Details
368
+ >>>
369
+ # Important Information
370
+ This is the content of the dialog.
371
+ ???
372
+ ```
373
+
374
+ The button displays "Show Details" and the dialog header shows "Important Information".
375
+
376
+ #### Dialog Options
377
+
378
+ You can customize dialog behavior with optional parameters:
379
+
380
+ **Light Dismiss** - Dialog closes when clicking the overlay:
381
+
382
+ ```markdown
383
+ ???light-dismiss
384
+ Open Dialog
385
+ >>>
386
+ Click outside to close this dialog.
387
+ ???
388
+ ```
389
+
390
+ **Without Header** - Remove the dialog header entirely:
391
+
392
+ ```markdown
393
+ ???without-header
394
+ Open Dialog
395
+ >>>
396
+ This dialog has no header.
397
+ ???
398
+ ```
399
+
400
+ **Custom Width** - Set a specific width using CSS units (px, em, rem, vw, vh, %):
401
+
402
+ ```markdown
403
+ ???600px
404
+ Open Dialog
405
+ >>>
406
+ This dialog is 600 pixels wide.
407
+ ???
408
+
409
+ ???50vw
410
+ Open Wide Dialog
411
+ >>>
412
+ This dialog is 50% of the viewport width.
413
+ ???
414
+
415
+ ???40em
416
+ Open Dialog
417
+ >>>
418
+ This dialog is 40em wide.
419
+ ???
420
+ ```
421
+
422
+ **Combining Options** - You can combine multiple options:
423
+
424
+ ```markdown
425
+ ???light-dismiss 700px
426
+ Open Dialog
427
+ >>>
428
+ # Custom Dialog
429
+ This dialog has light dismiss enabled and is 700px wide.
430
+ ???
431
+
432
+ ???without-header light-dismiss 45em
433
+ Open Simple Dialog
434
+ >>>
435
+ This dialog has no header, light dismiss, and is 45em wide.
436
+ ???
437
+ ```
438
+
439
+ > **Note**: The dialog uses Web Awesome's declarative `data-dialog` API, so no custom JavaScript is needed. Each dialog gets a unique ID automatically generated from its content, and a close button is automatically added to the footer.
440
+
335
441
  ### Details/Summary (Collapsible Content)
336
442
 
337
443
  Create collapsible content using the `^^^` syntax:
@@ -510,6 +616,58 @@ Cards automatically parse content into these slots:
510
616
  | `end` (default) | Icon appears on the right side |
511
617
  | `start` | Icon appears on the left side |
512
618
 
619
+ ### Dialog Parameters
620
+
621
+ | Option | Description |
622
+ |--------|-------------|
623
+ | `light-dismiss` | Dialog closes when clicking outside/on overlay |
624
+ | `without-header` | Removes the dialog header entirely |
625
+ | Width (e.g., `500px`, `50vw`, `40em`) | Sets custom width using CSS units (px, em, rem, vw, vh, %) |
626
+
627
+ ### Dialog Features
628
+
629
+ - **Automatic ID Generation**: Each dialog gets a unique ID based on MD5 hash of its content
630
+ - **Auto-close Button**: A "Close" button is automatically added to the footer
631
+ - **Declarative API**: Uses Web Awesome's `data-dialog` attributes - no custom JavaScript needed
632
+ - **Label Extraction**: First `#` heading becomes the dialog label, or button text is used as fallback
633
+ - **Markdown Support**: Full markdown formatting in dialog content
634
+
635
+ ### Image Dialogs (Auto-transformation)
636
+
637
+ Enable automatic image-to-dialog transformation in your `_config.yml`:
638
+
639
+ ```yaml
640
+ webawesome:
641
+ image_dialog: true
642
+ ```
643
+
644
+ When enabled, all markdown images automatically become clickable and open in full-size dialogs:
645
+
646
+ ```markdown
647
+ ![Architecture Diagram](diagram.png)
648
+ ```
649
+
650
+ **Control dialog width** by adding a width parameter to the title:
651
+
652
+ ```markdown
653
+ ![Diagram](diagram.png "50%") # Dialog width: 50%
654
+ ![Photo](photo.jpg "800px") # Dialog width: 800px
655
+ ![Wide](image.png "90vw") # Dialog width: 90% viewport
656
+ ```
657
+
658
+ Supported width units: `px`, `em`, `rem`, `vw`, `vh`, `%`, `ch`
659
+
660
+ **Opt-out** by adding `"nodialog"` to the title:
661
+
662
+ ```markdown
663
+ ![Small Icon](icon.svg "nodialog")
664
+ ```
665
+
666
+ **Features:**
667
+
668
+ - Light-dismiss and headerless dialogs for clean UX
669
+ - Thumbnail displays at original size, dialog shows full-size
670
+
513
671
  ### Tab Placements
514
672
 
515
673
  - `top` (default)
@@ -48,38 +48,38 @@ module Jekyll
48
48
  content.match?(callout_pattern) || content.match?(details_pattern) || content.match?(tabs_pattern)
49
49
  end
50
50
 
51
- # Transform code blocks from markdown syntax to Jekyll highlight tags
52
- def transform_code_blocks(content)
51
+ # Protect all code blocks by converting them to placeholders
52
+ # This prevents markdown processing from corrupting code blocks inside custom elements
53
+ def protect_all_code_blocks(content)
53
54
  counter = @@protected_blocks.size
54
55
 
55
- # First pass: protect markdown code blocks that contain WebAwesome syntax
56
- content = content.gsub(CODE_BLOCK_PATTERN) do |match|
57
- language = Regexp.last_match(1)
58
- code_content = Regexp.last_match(2).strip
59
-
60
- # If this is a markdown code block containing WebAwesome syntax, protect it
61
- if language && language.downcase == 'markdown' && contains_webawesome_syntax?(code_content)
62
- placeholder = "<!--PROTECTED_WEBAWESOME_EXAMPLE_#{counter}-->"
63
- @@protected_blocks[placeholder] = match
64
- counter += 1
65
- placeholder
66
- else
67
- match # Leave other code blocks for normal processing
68
- end
69
- end
70
-
71
- # Second pass: transform remaining code blocks normally
72
56
  content.gsub(CODE_BLOCK_PATTERN) do |match|
73
- language = Regexp.last_match(1)
74
- code_content = Regexp.last_match(2).strip
57
+ placeholder = "<!--PROTECTED_CODE_BLOCK_#{counter}-->"
58
+ @@protected_blocks[placeholder] = match
59
+ counter += 1
60
+ placeholder
61
+ end
62
+ end
75
63
 
76
- if language && language.downcase != 'plain'
77
- transformed = "{% highlight #{language} %}\n#{code_content}\n{% endhighlight %}"
78
- transformed
64
+ # Transform code blocks from markdown syntax to Jekyll highlight tags
65
+ # This should be called AFTER WebAwesome transformers have processed the content
66
+ def transform_code_blocks(content)
67
+ # Transform code blocks that were previously protected
68
+ @@protected_blocks.transform_values! do |match|
69
+ if match =~ CODE_BLOCK_PATTERN
70
+ language = Regexp.last_match(1)
71
+ code_content = Regexp.last_match(2).strip
72
+
73
+ if language && language.downcase != 'plain'
74
+ "{% highlight #{language} %}\n#{code_content}\n{% endhighlight %}"
75
+ else
76
+ match # Return original block if no language or 'plain'
77
+ end
79
78
  else
80
- match # Return original block if no language or 'plain'
79
+ match
81
80
  end
82
81
  end
82
+ content
83
83
  end
84
84
 
85
85
  # Restore protected WebAwesome example blocks after WaElementTransformer processing
@@ -101,23 +101,42 @@ module Jekyll
101
101
  CodeBlockTransformer.clear_protected_blocks
102
102
  end
103
103
 
104
- Jekyll::Hooks.register :documents, :pre_render, priority: 30 do |document|
104
+ # STEP 1: Protect all code blocks BEFORE any transformations (highest priority)
105
+ Jekyll::Hooks.register :documents, :pre_render, priority: 50 do |document|
106
+ next unless document.relative_path =~ /.*\.md$/i
107
+ next unless CodeBlockTransformer.transform_documents_enabled?(document.site)
108
+
109
+ puts "Jekyll::WebAwesome::CodeBlockTransformer protecting code blocks in document: #{document.relative_path}\n"
110
+ document.content = CodeBlockTransformer.protect_all_code_blocks(document.content)
111
+ end
112
+
113
+ Jekyll::Hooks.register :pages, :pre_render, priority: 50 do |page|
114
+ next unless page.relative_path =~ /.*\.md$/i
115
+ next unless CodeBlockTransformer.transform_pages_enabled?(page.site)
116
+
117
+ puts "Jekyll::WebAwesome::CodeBlockTransformer protecting code blocks in page: #{page.relative_path}\n"
118
+ page.content = CodeBlockTransformer.protect_all_code_blocks(page.content)
119
+ end
120
+
121
+ # STEP 2: Transform protected code blocks to Jekyll highlight syntax
122
+ # This happens AFTER WebAwesome transformers (priority 20) but BEFORE restoration
123
+ Jekyll::Hooks.register :documents, :pre_render, priority: 15 do |document|
105
124
  next unless document.relative_path =~ /.*\.md$/i
106
125
  next unless CodeBlockTransformer.transform_documents_enabled?(document.site)
107
126
 
108
- puts "Jekyll::WebAwesome::CodeBlockTransformer processing document: #{document.relative_path}\n"
127
+ puts "Jekyll::WebAwesome::CodeBlockTransformer transforming code blocks in document: #{document.relative_path}\n"
109
128
  document.content = CodeBlockTransformer.transform_code_blocks(document.content)
110
129
  end
111
130
 
112
- Jekyll::Hooks.register :pages, :pre_render, priority: 30 do |page|
131
+ Jekyll::Hooks.register :pages, :pre_render, priority: 15 do |page|
113
132
  next unless page.relative_path =~ /.*\.md$/i
114
133
  next unless CodeBlockTransformer.transform_pages_enabled?(page.site)
115
134
 
116
- puts "Jekyll::WebAwesome::CodeBlockTransformer processing page: #{page.relative_path}\n"
135
+ puts "Jekyll::WebAwesome::CodeBlockTransformer transforming code blocks in page: #{page.relative_path}\n"
117
136
  page.content = CodeBlockTransformer.transform_code_blocks(page.content)
118
137
  end
119
138
 
120
- # Register hooks to restore protected blocks after WaElementTransformer
139
+ # STEP 3: Restore protected blocks after transformation (lowest priority)
121
140
  Jekyll::Hooks.register :documents, :pre_render, priority: 10 do |document|
122
141
  next unless document.relative_path =~ /.*\.md$/i
123
142
  next unless CodeBlockTransformer.transform_documents_enabled?(document.site)
@@ -40,6 +40,18 @@ module Jekyll
40
40
  true
41
41
  end
42
42
 
43
+ # Check if image dialog transformation is enabled
44
+ def self.image_dialog_enabled?(site)
45
+ # Check plugin configuration first
46
+ return Jekyll::WebAwesome.configuration.image_dialog if Jekyll::WebAwesome.configuration
47
+
48
+ # Check site config
49
+ return site.config.dig('webawesome', 'image_dialog') if site.config.dig('webawesome', 'image_dialog') != nil
50
+
51
+ # Default to false (opt-in feature)
52
+ false
53
+ end
54
+
43
55
  # Check if a file is a markdown file
44
56
  def self.markdown_file?(filepath)
45
57
  filepath.to_s.match?(/\.md$/i)
@@ -51,7 +63,7 @@ module Jekyll
51
63
  next unless transform_documents_enabled?(document.site)
52
64
 
53
65
  puts "Jekyll::WebAwesome Processing document (pre-render): #{document.relative_path}\n" if debug_enabled?(document.site)
54
- document.content = Transformer.process(document.content)
66
+ document.content = Transformer.process(document.content, document.site)
55
67
  end
56
68
 
57
69
  Jekyll::Hooks.register :pages, :pre_render do |page|
@@ -59,7 +71,7 @@ module Jekyll
59
71
  next unless transform_pages_enabled?(page.site)
60
72
 
61
73
  puts "Jekyll::WebAwesome Processing page (pre-render): #{page.relative_path}\n" if debug_enabled?(page.site)
62
- page.content = Transformer.process(page.content)
74
+ page.content = Transformer.process(page.content, page.site)
63
75
  end
64
76
  end
65
77
  end
@@ -8,7 +8,7 @@ module Jekyll
8
8
  module WebAwesome
9
9
  # Main transformer that orchestrates all component transformers
10
10
  class Transformer
11
- def self.process(content)
11
+ def self.process(content, site = nil)
12
12
  content = BadgeTransformer.transform(content)
13
13
  content = ButtonTransformer.transform(content)
14
14
  content = CalloutTransformer.transform(content)
@@ -16,6 +16,11 @@ module Jekyll
16
16
  content = ComparisonTransformer.transform(content)
17
17
  content = CopyButtonTransformer.transform(content)
18
18
  content = DetailsTransformer.transform(content)
19
+
20
+ # Apply image dialog transformer BEFORE dialog transformer so it can generate dialog syntax
21
+ content = ImageDialogTransformer.transform(content) if site && Plugin.image_dialog_enabled?(site)
22
+
23
+ content = DialogTransformer.transform(content)
19
24
  content = IconTransformer.transform(content)
20
25
  content = TagTransformer.transform(content)
21
26
  TabsTransformer.transform(content)
@@ -0,0 +1,172 @@
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, without-header, and optional width (e.g., 500px, 50vw, 40em)
12
+ class DialogTransformer < BaseTransformer
13
+ def self.transform(content)
14
+ # Define both regex patterns - capture parameter string, button text, and content
15
+ # Params are on the same line as the opening delimiter
16
+ # Button text is on the next line(s) until >>>
17
+ # Content is everything after >>> until the closing delimiter
18
+ primary_regex = /^\?\?\?([^\n]*)$\n(.*?)\n^>>>$\n(.*?)\n^\?\?\?$/m
19
+ alternative_regex = /^:::wa-dialog([^\n]*)$\n(.*?)\n^>>>$\n(.*?)\n^:::$/m
20
+
21
+ # Define shared transformation logic
22
+ transform_proc = proc do |params_string, button_text, dialog_content|
23
+ button_text = button_text.strip
24
+ dialog_content = dialog_content.strip
25
+
26
+ # Parse parameters
27
+ light_dismiss, without_header, width = parse_parameters(params_string)
28
+
29
+ # Extract label from first heading or use button text
30
+ label, content_without_label = extract_label(dialog_content, button_text)
31
+
32
+ # Generate unique ID based on content
33
+ dialog_id = generate_dialog_id(button_text, dialog_content)
34
+
35
+ # Convert markdown to HTML
36
+ content_html = markdown_to_html(content_without_label)
37
+
38
+ # Build the dialog HTML
39
+ build_dialog_html(dialog_id, button_text, label, content_html,
40
+ light_dismiss, without_header, width)
41
+ end
42
+
43
+ # Apply both patterns
44
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
45
+ apply_multiple_patterns(content, patterns)
46
+ end
47
+
48
+ class << self
49
+ private
50
+
51
+ # Parse parameters from the params string
52
+ def parse_parameters(params_string)
53
+ return [false, false, nil] if params_string.nil? || params_string.strip.empty?
54
+
55
+ tokens = params_string.strip.split(/\s+/)
56
+
57
+ light_dismiss = tokens.include?('light-dismiss')
58
+ without_header = tokens.include?('without-header')
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, without_header, width]
70
+ end
71
+
72
+ # Extract label from first heading in content
73
+ def extract_label(content, default_label)
74
+ # Check if content starts with a heading
75
+ if content.match(/^#\s+(.+?)$/)
76
+ label = Regexp.last_match(1).strip
77
+ # Remove the heading from content
78
+ content_without_label = content.sub(/^#\s+.+?\n/, '').strip
79
+ [label, content_without_label]
80
+ else
81
+ [default_label, content]
82
+ end
83
+ end
84
+
85
+ # Generate a unique ID for the dialog using MD5 hash
86
+ def generate_dialog_id(button_text, content)
87
+ hash_input = "#{button_text}#{content}"
88
+ hash = Digest::MD5.hexdigest(hash_input)
89
+ "dialog-#{hash[0..7]}" # Use first 8 characters of hash
90
+ end
91
+
92
+ # Build the complete dialog HTML with trigger button
93
+ def build_dialog_html(dialog_id, button_text, label, content_html,
94
+ light_dismiss, without_header, width)
95
+ # Build dialog attributes
96
+ dialog_attrs = ["id='#{dialog_id}'"]
97
+ # Escape both HTML and attribute characters for label
98
+ dialog_attrs << "label='#{escape_attribute(escape_html(label))}'" unless without_header
99
+ dialog_attrs << 'without-header' if without_header
100
+ dialog_attrs << 'light-dismiss' if light_dismiss
101
+
102
+ # Build style attribute for width if specified
103
+ style_attr = width ? " style='--width: #{width}'" : ''
104
+
105
+ # Check if button contains an image (for image dialog support)
106
+ is_image_button = button_text.include?('<img')
107
+
108
+ # Build the HTML
109
+ html = []
110
+
111
+ # Add CSS Parts styling for image buttons to make them invisible
112
+ if is_image_button
113
+ button_id = "#{dialog_id}-btn"
114
+ html << '<style>'
115
+ html << " ##{button_id}::part(base) {"
116
+ html << ' padding: 0;'
117
+ html << ' margin: 0;'
118
+ html << ' border: none;'
119
+ html << ' background: transparent;'
120
+ html << ' box-shadow: none;'
121
+ html << ' color: inherit;'
122
+ html << ' min-width: 0;'
123
+ html << ' height: auto;'
124
+ html << ' }'
125
+ html << " ##{button_id}::part(base):hover {"
126
+ html << ' background: transparent;'
127
+ html << ' border-color: transparent;'
128
+ html << ' }'
129
+ html << " ##{button_id}::part(base):active {"
130
+ html << ' background: transparent;'
131
+ html << ' border-color: transparent;'
132
+ html << ' }'
133
+ html << '</style>'
134
+ end
135
+
136
+ # Trigger button
137
+ # Only allow HTML for image tags (for image dialog support), escape everything else for security
138
+ button_content = is_image_button ? button_text : escape_html(button_text)
139
+ button_id_attr = is_image_button ? " id='#{button_id}'" : ''
140
+ button_variant = is_image_button ? " variant='text'" : ''
141
+ html << "<wa-button#{button_id_attr}#{button_variant} data-dialog='open #{dialog_id}'>#{button_content}</wa-button>"
142
+
143
+ # Dialog element
144
+ html << "<wa-dialog #{dialog_attrs.join(' ')}#{style_attr}>"
145
+ html << content_html
146
+
147
+ # Footer with close button
148
+ html << "<wa-button slot='footer' variant='primary' data-dialog='close'>Close</wa-button>"
149
+
150
+ html << '</wa-dialog>'
151
+
152
+ html.join("\n")
153
+ end
154
+
155
+ # Escape HTML entities in text
156
+ def escape_html(text)
157
+ text.gsub('&', '&amp;')
158
+ .gsub('<', '&lt;')
159
+ .gsub('>', '&gt;')
160
+ .gsub('"', '&quot;')
161
+ .gsub("'", '&#39;')
162
+ end
163
+
164
+ # Escape attribute values
165
+ def escape_attribute(text)
166
+ text.gsub("'", '&apos;')
167
+ .gsub('"', '&quot;')
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require_relative 'base_transformer'
5
+
6
+ module Jekyll
7
+ module WebAwesome
8
+ # Transforms standalone images into clickable images that open in dialogs
9
+ # Images can opt-out by adding "nodialog" to the title attribute
10
+ # Example: ![Alt text](image.png "nodialog")
11
+ class ImageDialogTransformer < BaseTransformer
12
+ def self.transform(content)
13
+ # First, protect inline code blocks from transformation
14
+ protected_content, code_blocks = protect_inline_code(content)
15
+
16
+ # Match markdown images: ![alt](url) or ![alt](url "title")
17
+ # Capture alt text, URL, and optional title
18
+ # URL can contain spaces and special characters
19
+ image_regex = /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]*)")?\)/
20
+
21
+ result = protected_content.gsub(image_regex) do |match|
22
+ alt_text = Regexp.last_match(1)
23
+ image_url = Regexp.last_match(2).strip
24
+ title = Regexp.last_match(3)
25
+
26
+ # Skip transformation if title contains "nodialog"
27
+ if title&.include?('nodialog')
28
+ # Return original image without dialog
29
+ match
30
+ else
31
+ # Transform to clickable image with dialog
32
+ transform_to_dialog(alt_text, image_url, title)
33
+ end
34
+ end
35
+
36
+ # Restore protected inline code
37
+ restore_inline_code(result, code_blocks)
38
+ end
39
+
40
+ class << self
41
+ private
42
+
43
+ # Protect inline code from transformation
44
+ def protect_inline_code(content)
45
+ code_blocks = []
46
+ protected = content.gsub(/`[^`]+`/) do |match|
47
+ placeholder = "INLINE_CODE_#{code_blocks.length}"
48
+ code_blocks << match
49
+ placeholder
50
+ end
51
+ [protected, code_blocks]
52
+ end
53
+
54
+ # Restore protected inline code
55
+ def restore_inline_code(content, code_blocks)
56
+ code_blocks.each_with_index do |code, index|
57
+ content = content.gsub("INLINE_CODE_#{index}", code)
58
+ end
59
+ content
60
+ end
61
+
62
+ # Transform image into our custom dialog syntax
63
+ # This will be processed by DialogTransformer to create the actual wa-dialog
64
+ def transform_to_dialog(alt_text, image_url, title)
65
+ # Parse width from title if specified (e.g., "50%", "800px", "60vw")
66
+ width = extract_width_from_title(title)
67
+
68
+ # Build dialog parameters
69
+ params = %w[light-dismiss without-header]
70
+ params << width if width
71
+ params_string = params.join(' ')
72
+
73
+ # Build the button content - a styled image that acts as the trigger
74
+ # Add title attribute if provided and doesn't contain "nodialog" or width
75
+ title_attr = title && !title.include?('nodialog') && !contains_width?(title) ? " title=\"#{title}\"" : ''
76
+ button_content = "<img src=\"#{image_url}\" alt=\"#{alt_text}\" style=\"cursor: zoom-in; display: block; width: 100%; height: auto;\"#{title_attr} />"
77
+
78
+ # Build the dialog content - full-size image
79
+ dialog_content = "<img src=\"#{image_url}\" alt=\"#{alt_text}\" style=\"max-width: 100%; height: auto; display: block; margin: 0 auto;\" />"
80
+
81
+ # Use our custom dialog syntax that will be processed by DialogTransformer
82
+ # Format: ???params\nbutton_content\n>>>\ndialog_content\n???
83
+ result = []
84
+ result << "???#{params_string}"
85
+ result << button_content
86
+ result << '>>>'
87
+ result << dialog_content
88
+ result << '???'
89
+
90
+ result.join("\n")
91
+ end
92
+
93
+ # Extract width parameter from title attribute
94
+ def extract_width_from_title(title)
95
+ return nil unless title
96
+
97
+ # Match CSS width units: px, em, rem, vw, vh, %, ch
98
+ match = title.match(/(\d+(?:\.\d+)?(?:px|em|rem|vw|vh|%|ch))/)
99
+ match ? match[1] : nil
100
+ end
101
+
102
+ # Check if title contains a width value
103
+ def contains_width?(title)
104
+ return false unless title
105
+
106
+ title.match?(/\d+(?:\.\d+)?(?:px|em|rem|vw|vh|%|ch)/)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -11,6 +11,8 @@ require_relative 'transformers/card_transformer'
11
11
  require_relative 'transformers/comparison_transformer'
12
12
  require_relative 'transformers/copy_button_transformer'
13
13
  require_relative 'transformers/details_transformer'
14
+ require_relative 'transformers/dialog_transformer'
14
15
  require_relative 'transformers/icon_transformer'
16
+ require_relative 'transformers/image_dialog_transformer'
15
17
  require_relative 'transformers/tabs_transformer'
16
18
  require_relative 'transformers/tag_transformer'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module WebAwesome
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
@@ -23,7 +23,7 @@ module Jekyll
23
23
 
24
24
  # Configuration class for future extensibility
25
25
  class Configuration
26
- attr_accessor :debug_mode, :callout_icons, :custom_components, :transform_pages, :transform_documents
26
+ attr_accessor :debug_mode, :callout_icons, :custom_components, :transform_pages, :transform_documents, :image_dialog
27
27
 
28
28
  def initialize
29
29
  @debug_mode = false
@@ -31,6 +31,7 @@ module Jekyll
31
31
  @custom_components = {}
32
32
  @transform_pages = true
33
33
  @transform_documents = true
34
+ @image_dialog = false # Opt-in by default for safety
34
35
  end
35
36
 
36
37
  private
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-webawesome
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janne Waren
@@ -125,7 +125,9 @@ files:
125
125
  - lib/jekyll/webawesome/transformers/comparison_transformer.rb
126
126
  - lib/jekyll/webawesome/transformers/copy_button_transformer.rb
127
127
  - lib/jekyll/webawesome/transformers/details_transformer.rb
128
+ - lib/jekyll/webawesome/transformers/dialog_transformer.rb
128
129
  - lib/jekyll/webawesome/transformers/icon_transformer.rb
130
+ - lib/jekyll/webawesome/transformers/image_dialog_transformer.rb
129
131
  - lib/jekyll/webawesome/transformers/tabs_transformer.rb
130
132
  - lib/jekyll/webawesome/transformers/tag_transformer.rb
131
133
  - lib/jekyll/webawesome/version.rb