markawesome 0.11.0 → 0.13.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: 7ff219b440ba6edf0b45bffa865e4ff4bf8516d5f3c214aa8bdff33c41133565
4
- data.tar.gz: b22df3f6875fcae4e9a98f0b86e384c2841ad14942f85832f70c5521c6a4058e
3
+ metadata.gz: dde7893cda313985c975f86e5521db8a3f56268ebf8d0df5b304719e8db5b73b
4
+ data.tar.gz: 73e885ac60dfbe2b8734d44aa135fb345440c977390d8d3d7a22cc577328eadf
5
5
  SHA512:
6
- metadata.gz: 84a1023e4464efd824f30f1612ede362a7469af9e47a3484b645dd921cad2c24aeda58f51ca4b00727ad5d327015e1cb68ed7892df00c97fdacee42ad3809c9a
7
- data.tar.gz: 883d44d1df8777343f2ff998ef95f8a832a6333066eca8d9e782c6c9026884c1e56f9459ecbd40b30f1f6be716bf22773c225eb0384867b6ad8d2fe1bef5c8a4
6
+ metadata.gz: 1e4cab6567c4ef43c87472be12ec93d506fc7b4d47a9a7588aa4c99e28d58761e6705bbf56b0ed5efac032dff0960fa6af9d661042950aba516f76d8ee4881ac
7
+ data.tar.gz: 49df9a0d39e97272b3acd05b52980b86ace61eb00dd6be7e92d91dc90ec0c688b550e5b21e7999303c8f3dc6dd8188cac8d791c1fe5ad07baaac0ce10b23fd5c
data/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
 
5
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.13.0] - 2026-06-20
8
+
9
+ ### Added
10
+
11
+ - The standalone `:::wa-icon` block now accepts `family` (`classic`, `sharp`, `duotone`, `sharp-duotone`, `brands`), `variant` (`thin`, `light`, `regular`, `solid`), and `animation` (`beat`, `fade`, `beat-fade`, `bounce`, `flip`, `shake`, `spin`, `spin-pulse`, `spin-reverse`) as bare enumerated tokens after the icon name (order-independent, rightmost-wins, unknown tokens dropped — same parsing as button `variant`/`size`). The block body becomes the icon's accessible `label` (stripped, whitespace-collapsed, HTML-escaped); an empty body emits no `label`, leaving the icon presentational per Web Awesome's default. Emit order is deterministic: `name family variant animation label`.
12
+ - `CalloutTransformer` callouts can now override their icon's `family`/`variant`/`animation` using the same bare tokens on the callout line (e.g. `:::warning shake`). The historical `variant="solid"` default is preserved when no icon-variant token is given, so existing callouts render byte-identically. Works alongside the existing `icon:name` override.
13
+ - New shared `Markawesome::IconAttributes` module — single source of truth for the `<wa-icon>` family/variant/animation vocabulary (verified against Web Awesome 3.x), consumed by both `IconTransformer` and `CalloutTransformer`.
14
+
15
+ The inline `$$$name` form stays **name-only** (mid-prose, semantically decorative). Embedded `icon:slot:name` icons in button/tag/details are unchanged. Note: `family`/`variant` produce a *visible* weight/family change only with a Font Awesome Pro kit; the attributes are always emitted to the DOM regardless of kit tier (validated against the examples site's WA 3.8.0 kit, where `animation` and `label` are fully functional and the weight/family attributes reach the element without a visible glyph swap on the free tier).
16
+
17
+ ## [0.12.0] - 2026-06-19
18
+
19
+ ### Added
20
+
21
+ - `ButtonTransformer` link-form buttons now accept `target` (`_blank`, `_self`, `_parent`, `_top`) and a boolean `download` flag. When `target="_blank"` is set, `rel="noopener noreferrer"` is emitted automatically to guard against reverse tabnabbing. These attributes are emitted **only** on link-form buttons (markup wrapping a markdown link, e.g. `%%%brand _blank\n[Text](url)\n%%%`); written on a regular (non-link) button they are parsed but dropped, consistent with the existing unrecognized-token behavior. They are also absent from the plain-markdown degradation path (`render_as_markdown`), since a plain `[text](url)` can't express them. A `download:filename` value form is a possible future enhancement.
22
+
7
23
  ## [0.11.0] - 2026-06-16
8
24
 
9
25
  Aligns generated markup with Web Awesome 3.8.0 (latest). See `ROADMAP.md` for the remaining enhancement backlog from the same audit.
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markawesome
4
+ # Shared <wa-icon> attribute vocabulary (family/variant/animation) and emission helper.
5
+ # Values verified against Web Awesome 3.x. Adding a value later is a one-line change.
6
+ module IconAttributes
7
+ SCHEMA = {
8
+ family: %w[classic sharp duotone sharp-duotone brands],
9
+ variant: %w[thin light regular solid],
10
+ animation: %w[beat fade beat-fade bounce flip shake spin spin-pulse spin-reverse]
11
+ }.freeze
12
+
13
+ # Returns ordered ['family="…"', 'variant="…"', 'animation="…"'] for present keys.
14
+ def self.pairs(attributes)
15
+ %i[family variant animation].filter_map do |key|
16
+ "#{key}=\"#{attributes[key]}\"" if attributes[key]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -18,6 +18,11 @@ module Markawesome
18
18
  # - loading: loading (loading state)
19
19
  # - disabled: disabled (disabled state)
20
20
  #
21
+ # Link-form only (emitted only when content is a markdown link):
22
+ # - target: _blank, _self, _parent, _top (target="_blank" also emits
23
+ # rel="noopener noreferrer" automatically)
24
+ # - download: download (bare download attribute)
25
+ #
21
26
  # Link buttons: %%%brand\n[Text](url)\n%%%
22
27
  # Regular buttons: %%%brand large pill\nText\n%%%
23
28
  class ButtonTransformer < BaseTransformer
@@ -29,7 +34,9 @@ module Markawesome
29
34
  pill: %w[pill],
30
35
  caret: %w[caret],
31
36
  loading: %w[loading],
32
- disabled: %w[disabled]
37
+ disabled: %w[disabled],
38
+ target: %w[_blank _self _parent _top],
39
+ download: %w[download]
33
40
  }.freeze
34
41
 
35
42
  ICON_SLOTS = { default: 'start', slots: %w[start end] }.freeze
@@ -105,7 +112,9 @@ module Markawesome
105
112
  # Fix whitespace issues like in badges
106
113
  button_html = button_html.gsub(%r{(</\w+>)\s+}, '\1&nbsp;')
107
114
 
108
- "<wa-button#{attrs_string} href=\"#{link_url}\">#{icon_html}#{button_html}</wa-button>"
115
+ link_attrs_string = link_pass_through_attrs(attributes)
116
+
117
+ "<wa-button#{attrs_string} href=\"#{link_url}\"#{link_attrs_string}>#{icon_html}#{button_html}</wa-button>"
109
118
  else
110
119
  # It's a regular button
111
120
  button_html = markdown_to_html(content).strip
@@ -117,6 +126,17 @@ module Markawesome
117
126
  "<wa-button#{attrs_string}>#{icon_html}#{button_html}</wa-button>"
118
127
  end
119
128
  end
129
+
130
+ # Plain anchor pass-throughs, meaningful only on link-form buttons.
131
+ # Auto-emit rel="noopener noreferrer" with target="_blank" to guard
132
+ # against reverse tabnabbing.
133
+ def link_pass_through_attrs(attributes)
134
+ link_attrs = []
135
+ link_attrs << "target=\"#{attributes[:target]}\"" if attributes[:target]
136
+ link_attrs << 'rel="noopener noreferrer"' if attributes[:target] == '_blank'
137
+ link_attrs << 'download' if attributes[:download]
138
+ link_attrs.empty? ? '' : " #{link_attrs.join(' ')}"
139
+ end
120
140
  end
121
141
  end
122
142
  end
@@ -3,6 +3,7 @@
3
3
  require_relative 'base_transformer'
4
4
  require_relative '../attribute_parser'
5
5
  require_relative '../icon_slot_parser'
6
+ require_relative '../icon_attributes'
6
7
 
7
8
  module Markawesome
8
9
  # Transforms callout syntax into wa-callout elements
@@ -28,20 +29,20 @@ module Markawesome
28
29
  transform_proc = proc do |variant, extra_params, inner_content|
29
30
  actual_variant = VARIANT_ALIASES.fetch(variant, variant)
30
31
 
31
- # Parse icon tokens first, then pass remaining to AttributeParser
32
+ # Parse icon tokens first, then pass remaining to AttributeParser.
33
+ # CALLOUT_ATTRIBUTES and IconAttributes::SCHEMA namespaces are disjoint, so the
34
+ # same remaining-token string can be parsed against both independently.
32
35
  icon_result = IconSlotParser.parse(extra_params, ICON_SLOTS)
33
36
  extra_attrs = AttributeParser.parse(icon_result[:remaining], CALLOUT_ATTRIBUTES)
37
+ icon_attrs = AttributeParser.parse(icon_result[:remaining], IconAttributes::SCHEMA)
38
+ icon_attrs[:variant] ||= 'solid' # preserve historical default
34
39
 
35
40
  attr_parts = ["variant=\"#{actual_variant}\""]
36
41
  attr_parts << "appearance=\"#{extra_attrs[:appearance]}\"" if extra_attrs[:appearance]
37
42
  attr_parts << "size=\"#{extra_attrs[:size]}\"" if extra_attrs[:size]
38
43
 
39
- # Use custom icon if provided, otherwise use default variant icon
40
- icon_html = if icon_result[:icons]['icon']
41
- "<wa-icon slot=\"icon\" name=\"#{icon_result[:icons]['icon']}\" variant=\"solid\"></wa-icon>"
42
- else
43
- icon_for(actual_variant)
44
- end
44
+ icon_name = icon_result[:icons]['icon'] || icon_name_for(actual_variant)
45
+ icon_html = callout_icon_html(icon_name, icon_attrs)
45
46
  html_content = "#{icon_html}#{markdown_to_html(inner_content)}"
46
47
 
47
48
  "<wa-callout #{attr_parts.join(' ')}>#{html_content}</wa-callout>"
@@ -79,11 +80,16 @@ module Markawesome
79
80
  class << self
80
81
  private
81
82
 
82
- def icon_for(variant)
83
+ def icon_name_for(variant)
83
84
  config = Markawesome.configuration
84
85
  icons = config&.callout_icons || default_icons
85
- icon_name = icons[variant.to_sym]
86
- "<wa-icon slot=\"icon\" name=\"#{icon_name}\" variant=\"solid\"></wa-icon>"
86
+ icons[variant.to_sym]
87
+ end
88
+
89
+ def callout_icon_html(name, attributes)
90
+ parts = ['slot="icon"', "name=\"#{name}\""]
91
+ parts.concat(IconAttributes.pairs(attributes))
92
+ "<wa-icon #{parts.join(' ')}></wa-icon>"
87
93
  end
88
94
 
89
95
  def default_icons
@@ -1,17 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base_transformer'
4
+ require_relative '../attribute_parser'
5
+ require_relative '../icon_attributes'
4
6
 
5
7
  module Markawesome
6
8
  # Transforms icon syntax into wa-icon elements
7
- # Primary syntax: $$$icon-name
8
- # Alternative syntax: :::wa-icon icon-name
9
+ # Primary syntax: $$$icon-name (name only, decorative)
10
+ # Alternative syntax: :::wa-icon icon-name [family] [variant] [animation]\n[label]\n:::
9
11
  #
10
12
  # Examples:
11
13
  # $$$settings -> <wa-icon name="settings"></wa-icon>
12
14
  # $$$home -> <wa-icon name="home"></wa-icon>
13
15
  # $$$user-circle -> <wa-icon name="user-circle"></wa-icon>
16
+ # :::wa-icon star spin\n::: -> <wa-icon name="star" animation="spin"></wa-icon>
17
+ # :::wa-icon heart solid\nFavorite\n::: ->
18
+ # <wa-icon name="heart" variant="solid" label="Favorite"></wa-icon>
14
19
  class IconTransformer < BaseTransformer
20
+ # First-line params + optional multi-line body. The closer is anchored to a line
21
+ # start; the opener is intentionally not anchored so it still matches mid-prose.
22
+ ALTERNATIVE_REGEX = /:::wa-icon[ \t]+([^\n]*?)[ \t]*\n(.*?)^:::/m
15
23
  def self.transform(content)
16
24
  # Protect code blocks first
17
25
  protected_content, code_blocks = protect_code_blocks(content)
@@ -25,9 +33,16 @@ module Markawesome
25
33
  end
26
34
 
27
35
  # Apply alternative syntax transformation
28
- result = result.gsub(/:::wa-icon\s+([a-zA-Z0-9\-_]+)\s*\n:::/m) do
29
- icon_name = ::Regexp.last_match(1)
30
- build_icon_html(icon_name)
36
+ result = result.gsub(ALTERNATIVE_REGEX) do
37
+ first_line = ::Regexp.last_match(1)
38
+ raw_body = ::Regexp.last_match(2)
39
+
40
+ tokens = first_line.strip.split(/\s+/)
41
+ icon_name = tokens.shift # first token is always the name
42
+ attributes = AttributeParser.parse(tokens.join(' '), IconAttributes::SCHEMA)
43
+ label = normalize_label(raw_body)
44
+
45
+ build_icon_html(icon_name, attributes, label)
31
46
  end
32
47
 
33
48
  # Restore code blocks
@@ -39,7 +54,12 @@ module Markawesome
39
54
 
40
55
  # Drop primary-syntax icons entirely.
41
56
  result = protected_content.gsub(/\$\$\$([a-zA-Z0-9\-_]+)(?![a-zA-Z0-9\-_]|\s+name\b)/, '')
42
- result = result.gsub(/:::wa-icon\s+([a-zA-Z0-9\-_]+)\s*\n:::/m, '')
57
+
58
+ # A labeled block degrades to its label text (collapsed, not HTML-escaped — it
59
+ # re-enters a markdown stream); an unlabeled block degrades to ''.
60
+ result = result.gsub(ALTERNATIVE_REGEX) do
61
+ ::Regexp.last_match(2).to_s.strip.gsub(/\s+/, ' ')
62
+ end
43
63
 
44
64
  restore_code_blocks(result, code_blocks)
45
65
  end
@@ -47,12 +67,24 @@ module Markawesome
47
67
  class << self
48
68
  private
49
69
 
50
- def build_icon_html(icon_name)
51
- # Clean and validate icon name
52
- clean_name = icon_name.strip
70
+ def build_icon_html(icon_name, attributes = {}, label = nil)
71
+ parts = ["name=\"#{icon_name.strip}\""]
72
+ parts.concat(IconAttributes.pairs(attributes))
73
+ parts << "label=\"#{label}\"" if label && !label.empty?
74
+ "<wa-icon #{parts.join(' ')}></wa-icon>"
75
+ end
76
+
77
+ # Label is an attribute VALUE, not markup: strip, collapse whitespace, HTML-escape.
78
+ # Deliberately NOT run through markdown_to_html (unlike button/callout bodies).
79
+ def normalize_label(raw_body)
80
+ text = raw_body.to_s.strip.gsub(/\s+/, ' ')
81
+ text.empty? ? nil : escape_html(text)
82
+ end
53
83
 
54
- # Return the wa-icon element
55
- "<wa-icon name=\"#{clean_name}\"></wa-icon>"
84
+ # Same escape set as Dialog/Popover transformers.
85
+ def escape_html(text)
86
+ text.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
87
+ .gsub('"', '&quot;').gsub("'", '&#39;')
56
88
  end
57
89
 
58
90
  def protect_code_blocks(content)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Markawesome
4
- VERSION = '0.11.0'
4
+ VERSION = '0.13.0'
5
5
  end
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.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janne Waren
@@ -79,6 +79,7 @@ files:
79
79
  - lib/markawesome.rb
80
80
  - lib/markawesome/attribute_parser.rb
81
81
  - lib/markawesome/code_block_protector.rb
82
+ - lib/markawesome/icon_attributes.rb
82
83
  - lib/markawesome/icon_slot_parser.rb
83
84
  - lib/markawesome/plain_markdown_renderer.rb
84
85
  - lib/markawesome/transformer.rb