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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +158 -0
- data/lib/jekyll/webawesome/code_block_transformer.rb +49 -30
- data/lib/jekyll/webawesome/plugin.rb +14 -2
- data/lib/jekyll/webawesome/transformer.rb +6 -1
- data/lib/jekyll/webawesome/transformers/dialog_transformer.rb +172 -0
- data/lib/jekyll/webawesome/transformers/image_dialog_transformer.rb +111 -0
- data/lib/jekyll/webawesome/transformers.rb +2 -0
- data/lib/jekyll/webawesome/version.rb +1 -1
- data/lib/jekyll/webawesome.rb +2 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e958cd124d7cb081e95cc61a14db48b6c0059daa49fef318c452a3cab30fecfb
|
|
4
|
+
data.tar.gz: b531b0aa50c859837a7500fbdb45d0c46dc0c936163b139715592b60fc3b05c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
+

|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**Control dialog width** by adding a width parameter to the title:
|
|
651
|
+
|
|
652
|
+
```markdown
|
|
653
|
+
 # Dialog width: 50%
|
|
654
|
+
 # Dialog width: 800px
|
|
655
|
+
 # 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
|
+

|
|
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
|
-
#
|
|
52
|
-
|
|
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
|
-
|
|
74
|
-
|
|
57
|
+
placeholder = "<!--PROTECTED_CODE_BLOCK_#{counter}-->"
|
|
58
|
+
@@protected_blocks[placeholder] = match
|
|
59
|
+
counter += 1
|
|
60
|
+
placeholder
|
|
61
|
+
end
|
|
62
|
+
end
|
|
75
63
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
#
|
|
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('&', '&')
|
|
158
|
+
.gsub('<', '<')
|
|
159
|
+
.gsub('>', '>')
|
|
160
|
+
.gsub('"', '"')
|
|
161
|
+
.gsub("'", ''')
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Escape attribute values
|
|
165
|
+
def escape_attribute(text)
|
|
166
|
+
text.gsub("'", ''')
|
|
167
|
+
.gsub('"', '"')
|
|
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: 
|
|
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:  or 
|
|
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'
|
data/lib/jekyll/webawesome.rb
CHANGED
|
@@ -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
|
+
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
|