markawesome 0.1.0 → 0.2.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: 0bfe7629915ad2e621b40c4bc478c90f00313fe5f19ff46739cf1d9ddbff4583
4
- data.tar.gz: b898ba0b05ba42755ddee581d9cc10cfa8a405df6b29e0d7bfad9f6e86523ec3
3
+ metadata.gz: e7ee0dc5c92fb7e9774b22ff56152d96d47a5eaf16da5f5a0181eda5e61aae57
4
+ data.tar.gz: 321ab99ac9d4b1bc7355a898271bcaf14aa8edf7bbeeafebf3b307d2ce0dd041
5
5
  SHA512:
6
- metadata.gz: 24fc7fb6409b036053e0bfd475e1ea287c0cf34e28870ef11e2767ae78bb990c5a7038b5a84ecd776d976aeacc53afc531a6cab3a19a2f962f4845a8e27ea2e1
7
- data.tar.gz: 5efa1542577c5bbbaf6417cc4c0b145a6c66725eca661b3b48241e791000eff6a4f66c7d1aac29971f45cf20a152b7ece83597f9a8ae426f0ab3c4fea94d8465
6
+ metadata.gz: de9cc698634f6e9d741317fa48a98d46619869949ca4641615a08c55504e253ecd4c292e3cc4a6b89ff7c394f277b5671c22c58b38e1f5839b751147a2cc6c5a
7
+ data.tar.gz: 736f49201d6323f34cc9c4ca198db98a8cd34ff0c4ec891649aafce06360f497ea797a697f623ee93f23807461334e05fa5df1fe76b76d39019fdc363c3b109d
data/CHANGELOG.md CHANGED
@@ -2,24 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [0.1.0] - 2025-10-27
7
+ ## [0.2.0] - 2025-10-27
8
8
 
9
9
  ### Added
10
10
 
11
- - Initial release
12
- - Core transformer functionality extracted from jekyll-webawesome
13
- - Support for Web Awesome components:
14
- - Badges
15
- - Buttons
16
- - Callouts
17
- - Cards
18
- - Comparisons
19
- - Copy Buttons
20
- - Details
21
- - Icons
22
- - Tabs
23
- - Tags
24
- - Configuration system for customizing component behavior
25
- - Framework-agnostic Markdown to Web Awesome transformation
11
+ - Initial release of Markawesome
12
+ - Framework-agnostic Markdown to Web Awesome component transformation
13
+ - Support for Badge, Button, Callout, Card, Carousel, Comparison, Copy Button, Details, Dialog, Icon, Image Dialog, Tabs, and Tag components
14
+ - Configuration system for callout icons and custom components
15
+ - Extracted from jekyll-webawesome gem for reusability across frameworks
data/README.md CHANGED
@@ -1,8 +1,30 @@
1
1
  # Markawesome
2
2
 
3
- Markawesome is a Ruby library that transforms custom Markdown syntax into Web Awesome components. It provides a framework-agnostic way to convert Markdown to Web Awesome HTML.
3
+ A framework-agnostic Ruby library that transforms custom Markdown syntax into [Web Awesome](https://webawesome.com/) HTML components. Use it with Jekyll, Hugo, Middleman, or any static site generator that processes Markdown.
4
4
 
5
- Used as the transformation engine behind the [jekyll-webawesome](https://github.com/jannewaren/jekyll-webawesome) plugin for [Jekyll](https://jekyllrb.com/).
5
+ Used as the transformation engine for the [jekyll-webawesome](https://github.com/jannewaren/jekyll-webawesome) plugin.
6
+
7
+ ## Features
8
+
9
+ - 🎯 **Framework Agnostic** - Works with any static site generator or Ruby application
10
+ - 🚀 **Simple Syntax** - Clean, intuitive Markdown extensions
11
+ - ⚙️ **Configurable** - Customize icons, variants, and component behavior
12
+
13
+ ## Supported components
14
+
15
+ | Component | Primary Syntax | Alternative Syntax | HTML Output |
16
+ |-----------|----------------|-------------------|-------------|
17
+ | **Badge** | `!!!variant` | `:::wa-badge variant` | `<wa-badge variant="brand">content</wa-badge>` |
18
+ | **Button** | `%%%variant` | `:::wa-button variant` | `<wa-button variant="brand" href="url">text</wa-button>` or `<wa-button variant="brand">text</wa-button>` |
19
+ | **Callouts** | `:::info` | `:::wa-callout info` | `<wa-callout variant="brand"><wa-icon name="circle-info"></wa-icon>content</wa-callout>` |
20
+ | **Card** | `===` | `:::wa-card` | `<wa-card>content</wa-card>` |
21
+ | **Carousel** | `~~~~~~` | `:::wa-carousel` | `<wa-carousel>` with carousel items |
22
+ | **Comparison** | `\|\|\|` or `\|\|\|25` | `:::wa-comparison` or `:::wa-comparison 25` | `<wa-comparison>` with before/after slots |
23
+ | **Copy Button** | `<<<` | `:::wa-copy-button` | `<wa-copy-button value="content">content</wa-copy-button>` |
24
+ | **Details** | `^^^appearance? icon-placement?` | `:::wa-details appearance? icon-placement?` | `<wa-details appearance="..." icon-placement="...">content</wa-details>` |
25
+ | **Dialog** | `???params?` | `:::wa-dialog params?` | `<wa-dialog>` with trigger button and content |
26
+ | **Tab Group** | `++++++` | `:::wa-tabs` | `<wa-tab-group><wa-tab>content</wa-tab></wa-tab-group>` |
27
+ | **Tag** | `@@@brand` | `:::wa-tag brand` | `<wa-tag variant="brand">content</wa-tag>` |
6
28
 
7
29
  ## Installation
8
30
 
@@ -26,36 +48,31 @@ gem install markawesome
26
48
 
27
49
  ## Usage
28
50
 
51
+ ### Basic Usage
52
+
29
53
  ```ruby
30
54
  require 'markawesome'
31
55
 
32
- content = <<~MARKDOWN
56
+ markdown = <<~MD
57
+ !!!brand
58
+ New Feature
59
+ !!!
60
+
33
61
  :::info
34
- This is an info callout!
62
+ This is an informational callout
35
63
  :::
36
- MARKDOWN
64
+ MD
37
65
 
38
- transformed = Markawesome::Transformer.process(content)
66
+ html = Markawesome::Transformer.process(markdown)
39
67
  ```
40
68
 
41
- ## Supported Components
42
-
43
- | Component | Primary Syntax | Alternative Syntax | HTML Output |
44
- |-----------|----------------|-------------------|-------------|
45
- | **Badge** | `!!!variant` | `:::wa-badge variant` | `<wa-badge variant="brand">content</wa-badge>` |
46
- | **Button** | `%%%variant` | `:::wa-button variant` | `<wa-button variant="brand" href="url">text</wa-button>` or `<wa-button variant="brand">text</wa-button>` |
47
- | **Callouts** | `:::info` | `:::wa-callout info` | `<wa-callout variant="brand"><wa-icon name="circle-info"></wa-icon>content</wa-callout>` |
48
- | **Card** | `===` | `:::wa-card` | `<wa-card>content</wa-card>` |
49
- | **Comparison** | `\|\|\|` or `\|\|\|25` | `:::wa-comparison` or `:::wa-comparison 25` | `<wa-comparison>` with before/after slots |
50
- | **Copy Button** | `<<<` | `:::wa-copy-button` | `<wa-copy-button value="content">content</wa-copy-button>` |
51
- | **Details** | `^^^appearance? icon-placement?` | `:::wa-details appearance? icon-placement?` | `<wa-details appearance="..." icon-placement="...">content</wa-details>` |
52
- | **Tab Group** | `++++++` | `:::wa-tabs` | `<wa-tab-group><wa-tab>content</wa-tab></wa-tab-group>` |
53
- | **Tag** | `@@@brand` | `:::wa-tag brand` | `<wa-tag variant="brand">content</wa-tag>` |
69
+ ### Configuration
54
70
 
55
- ## Configuration
71
+ Configure Markawesome globally to customize icon names and add custom components:
56
72
 
57
73
  ```ruby
58
74
  Markawesome.configure do |config|
75
+ # Customize callout icons
59
76
  config.callout_icons = {
60
77
  info: 'circle-info',
61
78
  success: 'circle-check',
@@ -63,13 +80,32 @@ Markawesome.configure do |config|
63
80
  warning: 'triangle-exclamation',
64
81
  danger: 'circle-exclamation'
65
82
  }
83
+
84
+ # Add custom components (for future extensibility)
85
+ config.custom_components = {
86
+ 'my-component' => 'MyComponent'
87
+ }
66
88
  end
67
89
  ```
68
90
 
91
+ ### Image Dialog Feature
92
+
93
+ Transform images into clickable dialogs:
94
+
95
+ ```ruby
96
+ markdown = '![Diagram](diagram.png)'
97
+
98
+ # Enable image dialog transformation
99
+ html = Markawesome::Transformer.process(markdown, image_dialog: true)
100
+
101
+ # Or with configuration
102
+ html = Markawesome::Transformer.process(markdown, image_dialog: { default_width: '80vw' })
103
+ ```
104
+
69
105
  ## Contributing
70
106
 
71
107
  Bug reports and pull requests are welcome on GitHub at https://github.com/jannewaren/markawesome.
72
108
 
73
109
  ## License
74
110
 
75
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
111
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
@@ -6,14 +6,23 @@ require_relative 'transformers'
6
6
  module Markawesome
7
7
  # Main transformer that orchestrates all component transformers
8
8
  class Transformer
9
- def self.process(content)
9
+ def self.process(content, options = {})
10
10
  content = BadgeTransformer.transform(content)
11
11
  content = ButtonTransformer.transform(content)
12
12
  content = CalloutTransformer.transform(content)
13
13
  content = CardTransformer.transform(content)
14
+ content = CarouselTransformer.transform(content)
14
15
  content = ComparisonTransformer.transform(content)
15
16
  content = CopyButtonTransformer.transform(content)
16
17
  content = DetailsTransformer.transform(content)
18
+
19
+ # Apply image dialog transformer BEFORE dialog transformer if enabled
20
+ if options[:image_dialog]
21
+ config = options[:image_dialog].is_a?(Hash) ? options[:image_dialog] : {}
22
+ content = ImageDialogTransformer.transform(content, config)
23
+ end
24
+
25
+ content = DialogTransformer.transform(content)
17
26
  content = IconTransformer.transform(content)
18
27
  content = TagTransformer.transform(content)
19
28
  TabsTransformer.transform(content)
@@ -32,8 +32,9 @@ module Markawesome
32
32
  private
33
33
 
34
34
  def callout_attributes(variant)
35
+ # Get icon from configuration if available
35
36
  config = Markawesome.configuration
36
- icons = config&.callout_icons || default_callout_icons
37
+ icons = config&.callout_icons || default_icons
37
38
 
38
39
  case variant
39
40
  when 'info'
@@ -69,7 +70,7 @@ module Markawesome
69
70
  end
70
71
  end
71
72
 
72
- def default_callout_icons
73
+ def default_icons
73
74
  {
74
75
  info: 'circle-info',
75
76
  success: 'circle-check',
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+
5
+ module Markawesome
6
+ # Transforms carousel syntax into wa-carousel elements
7
+ # Primary syntax: ~~~~~~params\n~~~ slide1\ncontent\n~~~\n~~~ slide2\ncontent\n~~~\n~~~~~~
8
+ # Alternative syntax: :::wa-carousel params\n~~~ slide1\ncontent\n~~~\n~~~ slide2\ncontent\n~~~\n:::
9
+ # Params can include: numbers (slides-per-page, slides-per-move), keywords (loop, navigation, pagination,
10
+ # autoplay, mouse-dragging, vertical), and CSS properties (scroll-hint:value, aspect-ratio:value, slide-gap:value)
11
+ class CarouselTransformer < BaseTransformer
12
+ def self.transform(content)
13
+ # Define both regex patterns
14
+ # Match: ~~~~~~params (optional)
15
+ # ~~~
16
+ # content (can be empty)
17
+ # ~~~
18
+ # (repeat slides)
19
+ # ~~~~~~
20
+ primary_regex = /^~{6}([^\n]*)\n((?:~~~\n(?:.*?\n)?~~~\n?)+)~{6}/m
21
+ alternative_regex = /^:::wa-carousel\s*([^\n]*)\n((?:~~~\n(?:.*?\n)?~~~\n?)+):::/m
22
+
23
+ # Define shared transformation logic
24
+ transform_proc = proc do |params, slides_block, _third_capture|
25
+ parsed_params = parse_params(params)
26
+ slides = extract_slides(slides_block)
27
+
28
+ build_carousel_html(slides, parsed_params)
29
+ end
30
+
31
+ # Apply both patterns
32
+ patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
33
+ apply_multiple_patterns(content, patterns)
34
+ end
35
+
36
+ class << self
37
+ private
38
+
39
+ def parse_params(params)
40
+ return {} if params.nil? || params.strip.empty?
41
+
42
+ result = {
43
+ attributes: {},
44
+ css_vars: {}
45
+ }
46
+
47
+ tokens = params.strip.split(/\s+/)
48
+ numeric_count = 0
49
+
50
+ tokens.each do |token|
51
+ # Check for CSS custom properties (key:value)
52
+ if token.include?(':')
53
+ key, value = token.split(':', 2)
54
+ case key
55
+ when 'scroll-hint'
56
+ result[:css_vars]['--scroll-hint'] = value
57
+ when 'aspect-ratio'
58
+ # Support 'auto', 'none', or 'unset' to remove the default aspect ratio
59
+ # This is useful for text content or variable-height slides
60
+ result[:css_vars]['--aspect-ratio'] = value
61
+ when 'slide-gap'
62
+ result[:css_vars]['--slide-gap'] = value
63
+ end
64
+ # Check for numeric values
65
+ elsif token.match?(/^\d+$/)
66
+ numeric_count += 1
67
+ if numeric_count == 1
68
+ result[:attributes]['slides-per-page'] = token
69
+ elsif numeric_count == 2
70
+ result[:attributes]['slides-per-move'] = token
71
+ end
72
+ # Check for boolean flags
73
+ elsif %w[loop navigation pagination autoplay mouse-dragging vertical].include?(token)
74
+ # For orientation, we need to handle it specially
75
+ if token == 'vertical'
76
+ result[:attributes]['orientation'] = 'vertical'
77
+ else
78
+ result[:attributes][token] = true
79
+ end
80
+ end
81
+ end
82
+
83
+ result
84
+ end
85
+
86
+ def extract_slides(slides_block)
87
+ # Extract individual slides using ~~~ markers
88
+ # Handle both content and empty slides
89
+ slide_contents = slides_block.scan(/~~~\n(.*?)~~~(?:\n|$)/m)
90
+ slide_contents.map { |match| match[0].strip }
91
+ end
92
+
93
+ def build_carousel_html(slides, parsed_params)
94
+ attributes = build_attributes(parsed_params[:attributes] || {})
95
+ style = build_style(parsed_params[:css_vars] || {})
96
+
97
+ attr_string = attributes.empty? ? '' : " #{attributes.join(' ')}"
98
+ style_string = style.empty? ? '' : " style=\"#{style}\""
99
+
100
+ slide_items = slides.map do |slide_content|
101
+ slide_html = markdown_to_html(slide_content)
102
+ "<wa-carousel-item>#{slide_html}</wa-carousel-item>"
103
+ end
104
+
105
+ "<wa-carousel#{attr_string}#{style_string}>#{slide_items.join}</wa-carousel>"
106
+ end
107
+
108
+ def build_attributes(attrs)
109
+ return [] if attrs.nil? || attrs.empty?
110
+
111
+ attrs.map do |key, value|
112
+ if value == true
113
+ key
114
+ else
115
+ "#{key}=\"#{value}\""
116
+ end
117
+ end
118
+ end
119
+
120
+ def build_style(css_vars)
121
+ return '' if css_vars.nil? || css_vars.empty?
122
+
123
+ css_vars.map { |key, value| "#{key}: #{value}" }.join('; ')
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require_relative 'base_transformer'
5
+
6
+ module Markawesome
7
+ # Transforms dialog syntax into wa-dialog elements with trigger buttons
8
+ # Primary syntax: ???params\nbutton text\n>>>\ncontent\n???
9
+ # Alternative syntax: :::wa-dialog params\nbutton text\n>>>\ncontent\n:::
10
+ # Params: light-dismiss and optional width (e.g., 500px, 50vw, 40em)
11
+ # Note: Header with close X button is always enabled for accessibility
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, 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, 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, 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
+
59
+ # Look for width parameter (last token with CSS units)
60
+ width = nil
61
+ tokens.reverse_each do |token|
62
+ if token.match?(/^\d+(\.\d+)?(px|em|rem|vw|vh|%|ch)$/)
63
+ width = token
64
+ break
65
+ end
66
+ end
67
+
68
+ [light_dismiss, width]
69
+ end
70
+
71
+ # Extract label from first heading in content
72
+ # Always returns a label - uses heading if available, otherwise default_label
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
+ # Use default label (button text) to ensure header is always shown
82
+ [default_label, content]
83
+ end
84
+ end
85
+
86
+ # Generate a unique ID for the dialog using MD5 hash
87
+ def generate_dialog_id(button_text, content)
88
+ hash_input = "#{button_text}#{content}"
89
+ hash = Digest::MD5.hexdigest(hash_input)
90
+ "dialog-#{hash[0..7]}" # Use first 8 characters of hash
91
+ end
92
+
93
+ # Build the complete dialog HTML with trigger button
94
+ # Header with X close button is always enabled for accessibility
95
+ def build_dialog_html(dialog_id, button_text, label, content_html,
96
+ light_dismiss, width)
97
+ # Build dialog attributes
98
+ dialog_attrs = ["id='#{dialog_id}'"]
99
+ # Escape both HTML and attribute characters for label
100
+ # Header is always shown to provide the X close button
101
+ dialog_attrs << "label='#{escape_attribute(escape_html(label))}'"
102
+ dialog_attrs << 'light-dismiss' if light_dismiss
103
+
104
+ # Build style attribute for width if specified
105
+ style_attr = width ? " style='--width: #{width}'" : ''
106
+
107
+ # Check if button contains an image (for image dialog support)
108
+ is_image_button = button_text.include?('<img')
109
+
110
+ # Build the HTML
111
+ html = []
112
+
113
+ # Add CSS Parts styling for image buttons to make them invisible
114
+ if is_image_button
115
+ button_id = "#{dialog_id}-btn"
116
+ html << '<style>'
117
+ html << " ##{button_id}::part(base) {"
118
+ html << ' padding: 0;'
119
+ html << ' margin: 0;'
120
+ html << ' border: none;'
121
+ html << ' background: transparent;'
122
+ html << ' box-shadow: none;'
123
+ html << ' color: inherit;'
124
+ html << ' min-width: 0;'
125
+ html << ' height: auto;'
126
+ html << ' }'
127
+ html << " ##{button_id}::part(base):hover {"
128
+ html << ' background: transparent;'
129
+ html << ' border-color: transparent;'
130
+ html << ' }'
131
+ html << " ##{button_id}::part(base):active {"
132
+ html << ' background: transparent;'
133
+ html << ' border-color: transparent;'
134
+ html << ' }'
135
+ html << '</style>'
136
+ end
137
+
138
+ # Trigger button
139
+ # Only allow HTML for image tags (for image dialog support), escape everything else for security
140
+ button_content = is_image_button ? button_text : escape_html(button_text)
141
+ button_id_attr = is_image_button ? " id='#{button_id}'" : ''
142
+ button_variant = is_image_button ? " variant='text'" : ''
143
+ html << "<wa-button#{button_id_attr}#{button_variant} data-dialog='open #{dialog_id}'>#{button_content}</wa-button>"
144
+
145
+ # Dialog element
146
+ html << "<wa-dialog #{dialog_attrs.join(' ')}#{style_attr}>"
147
+ html << content_html
148
+
149
+ # Footer with close button
150
+ html << "<wa-button slot='footer' variant='primary' data-dialog='close'>Close</wa-button>"
151
+
152
+ html << '</wa-dialog>'
153
+
154
+ html.join("\n")
155
+ end
156
+
157
+ # Escape HTML entities in text
158
+ def escape_html(text)
159
+ text.gsub('&', '&amp;')
160
+ .gsub('<', '&lt;')
161
+ .gsub('>', '&gt;')
162
+ .gsub('"', '&quot;')
163
+ .gsub("'", '&#39;')
164
+ end
165
+
166
+ # Escape attribute values
167
+ def escape_attribute(text)
168
+ text.gsub("'", '&apos;')
169
+ .gsub('"', '&quot;')
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require_relative 'base_transformer'
5
+
6
+ module Markawesome
7
+ # Transforms standalone images into clickable images that open in dialogs
8
+ # Images can opt-out by adding "nodialog" to the title attribute
9
+ # Example: ![Alt text](image.png "nodialog")
10
+ class ImageDialogTransformer < BaseTransformer
11
+ def self.transform(content, config = {})
12
+ # First, protect code blocks, inline code, and comparison blocks from transformation
13
+ protected_content, fenced_code_blocks = protect_fenced_code_blocks(content)
14
+ protected_content, inline_code_blocks = protect_inline_code(protected_content)
15
+ protected_content, comparison_blocks = protect_comparisons(protected_content)
16
+
17
+ # Match markdown images: ![alt](url) or ![alt](url "title")
18
+ # Capture alt text, URL, and optional title
19
+ # URL can contain spaces and special characters
20
+ image_regex = /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]*)")?\)/
21
+
22
+ result = protected_content.gsub(image_regex) do |match|
23
+ alt_text = Regexp.last_match(1)
24
+ image_url = Regexp.last_match(2).strip
25
+ title = Regexp.last_match(3)
26
+
27
+ # Skip transformation if title contains "nodialog"
28
+ if title&.include?('nodialog')
29
+ # Return original image without dialog
30
+ match
31
+ else
32
+ # Transform to clickable image with dialog
33
+ transform_to_dialog(alt_text, image_url, title, config)
34
+ end
35
+ end
36
+
37
+ # Restore protected blocks in reverse order
38
+ result = restore_comparisons(result, comparison_blocks)
39
+ result = restore_inline_code(result, inline_code_blocks)
40
+ restore_fenced_code_blocks(result, fenced_code_blocks)
41
+ end
42
+
43
+ class << self
44
+ private
45
+
46
+ # Protect fenced code blocks from transformation
47
+ def protect_fenced_code_blocks(content)
48
+ code_blocks = []
49
+ # Match both ``` and ~~~ style code blocks with optional language
50
+ protected = content.gsub(/^```.*?^```$|^~~~.*?^~~~$/m) do |match|
51
+ placeholder = "<!--IMAGE_DIALOG_FENCED_CODE_#{code_blocks.length}-->"
52
+ code_blocks << match
53
+ placeholder
54
+ end
55
+ [protected, code_blocks]
56
+ end
57
+
58
+ # Restore protected fenced code blocks
59
+ def restore_fenced_code_blocks(content, code_blocks)
60
+ code_blocks.each_with_index do |code, index|
61
+ content = content.gsub("<!--IMAGE_DIALOG_FENCED_CODE_#{index}-->", code)
62
+ end
63
+ content
64
+ end
65
+
66
+ # Protect inline code from transformation
67
+ def protect_inline_code(content)
68
+ code_blocks = []
69
+ protected = content.gsub(/`[^`]+`/) do |match|
70
+ placeholder = "<!--IMAGE_DIALOG_INLINE_CODE_#{code_blocks.length}-->"
71
+ code_blocks << match
72
+ placeholder
73
+ end
74
+ [protected, code_blocks]
75
+ end
76
+
77
+ # Restore protected inline code
78
+ def restore_inline_code(content, code_blocks)
79
+ code_blocks.each_with_index do |code, index|
80
+ content = content.gsub("<!--IMAGE_DIALOG_INLINE_CODE_#{index}-->", code)
81
+ end
82
+ content
83
+ end
84
+
85
+ # Protect comparison blocks from image transformation
86
+ # Must protect both markdown syntax (|||...|||) and already-transformed HTML
87
+ def protect_comparisons(content)
88
+ comparison_blocks = []
89
+
90
+ # First protect markdown comparison syntax: |||...|||
91
+ protected = content.gsub(/\|\|\|(\d+)?\n.*?\n\|\|\|/m) do |match|
92
+ placeholder = "<!--IMAGE_DIALOG_COMPARISON_#{comparison_blocks.length}-->"
93
+ comparison_blocks << match
94
+ placeholder
95
+ end
96
+
97
+ # Also protect already-transformed HTML comparison blocks: <wa-comparison ...>...</wa-comparison>
98
+ protected = protected.gsub(/<wa-comparison[^>]*>.*?<\/wa-comparison>/m) do |match|
99
+ placeholder = "<!--IMAGE_DIALOG_COMPARISON_#{comparison_blocks.length}-->"
100
+ comparison_blocks << match
101
+ placeholder
102
+ end
103
+
104
+ [protected, comparison_blocks]
105
+ end
106
+
107
+ # Restore protected comparison blocks
108
+ def restore_comparisons(content, comparison_blocks)
109
+ comparison_blocks.each_with_index do |block, index|
110
+ content = content.gsub("<!--IMAGE_DIALOG_COMPARISON_#{index}-->", block)
111
+ end
112
+ content
113
+ end
114
+
115
+ # Transform image into our custom dialog syntax
116
+ # This will be processed by DialogTransformer to create the actual wa-dialog
117
+ def transform_to_dialog(alt_text, image_url, title, config = {})
118
+ # Parse width from title if specified (e.g., "50%", "800px", "60vw")
119
+ width = extract_width_from_title(title)
120
+
121
+ # Use default width from config if no width specified in title
122
+ width ||= config[:default_width] if config[:default_width]
123
+
124
+ # Build dialog parameters
125
+ # Always include header with X close button for accessibility
126
+ params = ['light-dismiss']
127
+ params << width if width
128
+ params_string = params.join(' ')
129
+
130
+ # Build the button content - a styled image that acts as the trigger
131
+ # Add title attribute if provided and doesn't contain "nodialog" or width
132
+ title_attr = title && !title.include?('nodialog') && !contains_width?(title) ? " title=\"#{title}\"" : ''
133
+ button_content = "<img src=\"#{image_url}\" alt=\"#{alt_text}\" style=\"cursor: zoom-in; display: block; width: 100%; height: auto;\"#{title_attr} />"
134
+
135
+ # Build the dialog content with alt text as heading for the label
136
+ # Use alt text for the label, or "Image" as fallback if alt is empty
137
+ label_text = alt_text.empty? ? 'Image' : alt_text
138
+ dialog_content = "# #{label_text}\n\n<img src=\"#{image_url}\" alt=\"#{alt_text}\" style=\"max-width: 100%; height: auto; display: block; margin: 0 auto;\" />"
139
+
140
+ # Use our custom dialog syntax that will be processed by DialogTransformer
141
+ # Format: ???params\nbutton_content\n>>>\ndialog_content\n???
142
+ result = []
143
+ result << "???#{params_string}"
144
+ result << button_content
145
+ result << '>>>'
146
+ result << dialog_content
147
+ result << '???'
148
+
149
+ result.join("\n")
150
+ end
151
+
152
+ # Extract width parameter from title attribute
153
+ def extract_width_from_title(title)
154
+ return nil unless title
155
+
156
+ # Match CSS width units: px, em, rem, vw, vh, %, ch
157
+ match = title.match(/(\d+(?:\.\d+)?(?:px|em|rem|vw|vh|%|ch))/)
158
+ match ? match[1] : nil
159
+ end
160
+
161
+ # Check if title contains a width value
162
+ def contains_width?(title)
163
+ return false unless title
164
+
165
+ title.match?(/\d+(?:\.\d+)?(?:px|em|rem|vw|vh|%|ch)/)
166
+ end
167
+ end
168
+ end
169
+ end
@@ -5,13 +5,18 @@ require_relative 'base_transformer'
5
5
  module Markawesome
6
6
  # Transforms tag syntax into wa-tag elements
7
7
  # Primary syntax: @@@variant?\ncontent\n@@@
8
+ # Inline syntax: @@@ variant? content @@@
8
9
  # Alternative syntax: :::wa-tag variant?\ncontent\n:::
9
10
  # Variants: brand, success, neutral, warning, danger
10
11
  class TagTransformer < BaseTransformer
11
12
  def self.transform(content)
12
- # Define both regex patterns
13
- primary_regex = /^@@@(brand|success|neutral|warning|danger)?\n(.*?)\n@@@/m
14
- alternative_regex = /^:::wa-tag\s*(brand|success|neutral|warning|danger)?\n(.*?)\n:::/m
13
+ # Define regex patterns
14
+ # Block syntax (multiline with newlines) - supports both LF and CRLF
15
+ primary_regex = /^@@@(brand|success|neutral|warning|danger)?\r?\n(.*?)\r?\n@@@/m
16
+ alternative_regex = /^:::wa-tag\s*(brand|success|neutral|warning|danger)?\r?\n(.*?)\r?\n:::/m
17
+
18
+ # Inline syntax (same line with spaces)
19
+ inline_regex = /@@@\s*(brand|success|neutral|warning|danger)?\s+([^@\r\n]+?)\s+@@@/
15
20
 
16
21
  # Define shared transformation logic
17
22
  transform_proc = proc do |variant, tag_content|
@@ -20,8 +25,17 @@ module Markawesome
20
25
  build_tag_html(tag_content, variant)
21
26
  end
22
27
 
23
- # Apply both patterns
24
- patterns = dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
28
+ # Apply all patterns (inline first to avoid conflicts)
29
+ patterns = [
30
+ {
31
+ regex: inline_regex,
32
+ block: proc do |_match, matchdata|
33
+ captures = matchdata.captures
34
+ transform_proc.call(*captures)
35
+ end
36
+ },
37
+ *dual_syntax_patterns(primary_regex, alternative_regex, transform_proc)
38
+ ]
25
39
  apply_multiple_patterns(content, patterns)
26
40
  end
27
41
 
@@ -8,9 +8,12 @@ require_relative 'transformers/badge_transformer'
8
8
  require_relative 'transformers/button_transformer'
9
9
  require_relative 'transformers/callout_transformer'
10
10
  require_relative 'transformers/card_transformer'
11
+ require_relative 'transformers/carousel_transformer'
11
12
  require_relative 'transformers/comparison_transformer'
12
13
  require_relative 'transformers/copy_button_transformer'
13
14
  require_relative 'transformers/details_transformer'
15
+ require_relative 'transformers/dialog_transformer'
14
16
  require_relative 'transformers/icon_transformer'
17
+ require_relative 'transformers/image_dialog_transformer'
15
18
  require_relative 'transformers/tabs_transformer'
16
19
  require_relative 'transformers/tag_transformer'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Markawesome
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/markawesome.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative 'markawesome/version'
4
4
  require_relative 'markawesome/transformer'
5
5
 
6
+ # Main module for Markawesome - framework-agnostic Markdown to Web Awesome component transformer
6
7
  module Markawesome
7
8
  class Error < StandardError; end
8
9
 
@@ -17,7 +18,7 @@ module Markawesome
17
18
  end
18
19
  end
19
20
 
20
- # Configuration class for customizing transformation behavior
21
+ # Configuration class for customizing transformations
21
22
  class Configuration
22
23
  attr_accessor :callout_icons, :custom_components
23
24
 
data/markawesome.gemspec CHANGED
@@ -8,8 +8,9 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ['Janne Waren']
9
9
  spec.email = ['janne.waren@iki.fi']
10
10
 
11
- spec.summary = 'Markdown to Web Awesome transformer'
12
- spec.description = 'A library that transforms custom Markdown syntax into Web Awesome components.'
11
+ spec.summary = 'Framework-agnostic Markdown to Web Awesome component transformer'
12
+ spec.description = 'A library that transforms custom Markdown syntax into Web Awesome components. ' \
13
+ 'Framework-agnostic and can be used with Jekyll, Hugo, or any other static site generator.'
13
14
  spec.homepage = 'https://github.com/jannewaren/markawesome'
14
15
  spec.license = 'MIT'
15
16
  spec.required_ruby_version = '>= 3.2'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markawesome
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janne Waren
@@ -80,6 +80,7 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '1.0'
82
82
  description: A library that transforms custom Markdown syntax into Web Awesome components.
83
+ Framework-agnostic and can be used with Jekyll, Hugo, or any other static site generator.
83
84
  email:
84
85
  - janne.waren@iki.fi
85
86
  executables: []
@@ -97,10 +98,13 @@ files:
97
98
  - lib/markawesome/transformers/button_transformer.rb
98
99
  - lib/markawesome/transformers/callout_transformer.rb
99
100
  - lib/markawesome/transformers/card_transformer.rb
101
+ - lib/markawesome/transformers/carousel_transformer.rb
100
102
  - lib/markawesome/transformers/comparison_transformer.rb
101
103
  - lib/markawesome/transformers/copy_button_transformer.rb
102
104
  - lib/markawesome/transformers/details_transformer.rb
105
+ - lib/markawesome/transformers/dialog_transformer.rb
103
106
  - lib/markawesome/transformers/icon_transformer.rb
107
+ - lib/markawesome/transformers/image_dialog_transformer.rb
104
108
  - lib/markawesome/transformers/tabs_transformer.rb
105
109
  - lib/markawesome/transformers/tag_transformer.rb
106
110
  - lib/markawesome/version.rb
@@ -128,5 +132,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
132
  requirements: []
129
133
  rubygems_version: 3.6.9
130
134
  specification_version: 4
131
- summary: Markdown to Web Awesome transformer
135
+ summary: Framework-agnostic Markdown to Web Awesome component transformer
132
136
  test_files: []