markawesome 0.13.0 → 0.14.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: dde7893cda313985c975f86e5521db8a3f56268ebf8d0df5b304719e8db5b73b
4
- data.tar.gz: 73e885ac60dfbe2b8734d44aa135fb345440c977390d8d3d7a22cc577328eadf
3
+ metadata.gz: afd9d40552aa4f47c46ce12a72e4340075e9049a0d374dabbd894d6b4e9c0771
4
+ data.tar.gz: bf63ff67375e59593d352c35b9e6852fd4f436a4005eba52bdf695b54d734f11
5
5
  SHA512:
6
- metadata.gz: 1e4cab6567c4ef43c87472be12ec93d506fc7b4d47a9a7588aa4c99e28d58761e6705bbf56b0ed5efac032dff0960fa6af9d661042950aba516f76d8ee4881ac
7
- data.tar.gz: 49df9a0d39e97272b3acd05b52980b86ace61eb00dd6be7e92d91dc90ec0c688b550e5b21e7999303c8f3dc6dd8188cac8d791c1fe5ad07baaac0ce10b23fd5c
6
+ metadata.gz: eeb2dffdb406f0eeb414cedfe834a616540956dddf5854c972b9743088e62b9d92054a4a1db2be1dc68817b5b81bec1ef7f0b30a0055a5508c6641710b8dc5f1
7
+ data.tar.gz: c76afeb5d61c70cab00f10e8a3a0d9b2f825bf04b5c4e5f46915506092fb826c9f1e4cb49061da3c29437404c2c0d2188eaf38d634704a56f6398a3410438770
data/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ 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.14.0] - 2026-06-24
8
+
9
+ ### Added
10
+
11
+ - New `AccordionTransformer` producing Web Awesome `<wa-accordion>` / `<wa-accordion-item>` — a multi-section collapsible container for FAQs, docs sections, and "show more" content (the grouped, mutually-exclusive-capable sibling of `details`). The `/` delimiter is the container/item marker: container fence `//////` (6×), item fence `///` (3×) with an explicit `///` close, mirroring tabs (`++++++`/`+++`). An alternative `:::wa-accordion … :::` form is also accepted.
12
+ - **Container line** (bare tokens, order-independent, rightmost-wins): `appearance` (`outlined` default, `filled`, `filled-outlined`, `plain`), `mode` (`multiple`, `single`, `single-collapsible`), `icon-placement` (`start`, `end`), and a value-form `heading:N` → `heading-level="N"` where `N` is `1`–`6` or `none` (out-of-range values fall back to omitting the attribute). `appearance` is always emitted (defaulting to `outlined`); `mode`, `icon-placement`, and `heading-level` are emitted only when specified.
13
+ - **Item header** (`/// [expanded] [disabled] [icon:name] Label`): leading `expanded`/`disabled` flags become the matching boolean attributes; `icon:name` becomes a `<wa-icon slot="icon" name="…">` first child; the remaining text becomes the HTML-escaped `label`. Item bodies are full markdown and may contain other components.
14
+ - **Plain-markdown degradation** (`render_as_markdown`, used for `.md` endpoints / llms.txt): each item degrades to a `### Label` heading followed by its body, mirroring tabs.
15
+ - `wa-accordion` is **experimental** in Web Awesome (added in 3.7), but `expanded`, `disabled`, and the `mode`/`appearance`/`icon-placement`/`heading-level` attributes are all declarative and static-site-safe — no JavaScript required. The interactive `expandAll()`/`collapseAll()` controls and rich/HTML `label` slot are out of scope for v1; item labels are plain text. The `--wa-accordion-divider-color` custom property removed in WA 3.9.0 is intentionally not exposed.
16
+
7
17
  ## [0.13.0] - 2026-06-20
8
18
 
9
19
  ### Added
@@ -24,8 +24,8 @@ module Markawesome
24
24
  # plugin during boot to override the default rendering for a single
25
25
  # component without forking the gem.
26
26
  #
27
- # @param component [Symbol] one of :callout, :badge, :button, :card,
28
- # :carousel, :comparison, :copy_button, :details, :dialog, :icon,
27
+ # @param component [Symbol] one of :accordion, :callout, :badge, :button,
28
+ # :card, :carousel, :comparison, :copy_button, :details, :dialog, :icon,
29
29
  # :image_dialog, :layout, :popover, :tabs, :tag.
30
30
  # @yield [content, options] Proc that receives the full source content
31
31
  # and the renderer options; returns the content with that component
@@ -58,6 +58,7 @@ module Markawesome
58
58
  icon
59
59
  tag
60
60
  tabs
61
+ accordion
61
62
  ].freeze
62
63
 
63
64
  TRANSFORMER_MAP = {
@@ -75,7 +76,8 @@ module Markawesome
75
76
  dialog: DialogTransformer,
76
77
  icon: IconTransformer,
77
78
  tag: TagTransformer,
78
- tabs: TabsTransformer
79
+ tabs: TabsTransformer,
80
+ accordion: AccordionTransformer
79
81
  }.freeze
80
82
 
81
83
  def self.process(content, options = {})
@@ -32,6 +32,10 @@ module Markawesome
32
32
  content = TagTransformer.transform(content)
33
33
  content = TabsTransformer.transform(content)
34
34
 
35
+ # Accordion runs last so item bodies may contain other already-transformed
36
+ # components (same reason tabs runs near the end).
37
+ content = AccordionTransformer.transform(content)
38
+
35
39
  CodeBlockProtector.restore(content, tokens)
36
40
  end
37
41
  end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_transformer'
4
+ require_relative '../attribute_parser'
5
+ require_relative '../icon_slot_parser'
6
+
7
+ module Markawesome
8
+ # Transforms accordion syntax into wa-accordion / wa-accordion-item elements
9
+ # Primary syntax:
10
+ # //////appearance? mode? icon-placement? heading:N?
11
+ # /// [expanded] [disabled] [icon:name] Label text
12
+ # item body markdown
13
+ # ///
14
+ # //////
15
+ # Alternative syntax: :::wa-accordion ...same items... :::
16
+ #
17
+ # Container attributes (bare, order-independent, rightmost-wins):
18
+ # - appearance: outlined (default), filled, filled-outlined, plain
19
+ # - mode: multiple (default), single, single-collapsible
20
+ # - icon-placement: start, end (default emitted only when given)
21
+ # - heading:N -> heading-level="N" where N is 1-6 or "none"
22
+ # Item tokens (leading flags, then the rest of the line is the label):
23
+ # - expanded -> expanded; disabled -> disabled
24
+ # - icon:name -> <wa-icon slot="icon" name="name"></wa-icon> as first child
25
+ #
26
+ # wa-accordion is experimental in Web Awesome (added 3.7). expanded/disabled
27
+ # and the mode/appearance/icon-placement attributes are all static-safe.
28
+ class AccordionTransformer < BaseTransformer
29
+ CONTAINER_ATTRIBUTES = {
30
+ appearance: %w[outlined filled filled-outlined plain],
31
+ mode: %w[multiple single single-collapsible],
32
+ icon_placement: %w[start end]
33
+ }.freeze
34
+
35
+ ICON_SLOTS = {
36
+ default: 'icon',
37
+ slots: %w[icon],
38
+ slot_map: { 'icon' => 'icon' }
39
+ }.freeze
40
+
41
+ ITEM_FLAGS = %w[expanded disabled].freeze
42
+
43
+ PRIMARY_REGEX = %r{^/{6}([^\n]*)\n((?:/{3} [^\n]+\n.*?\n/{3}\n?)+)/{6}}m
44
+ ALTERNATIVE_REGEX = %r{^:::wa-accordion\s*([^\n]*)\n((?:/{3} [^\n]+\n.*?\n/{3}\n?)+):::}m
45
+ ITEM_REGEX = %r{^/{3} ([^\n]+)\n(.*?)\n/{3}}m
46
+
47
+ def self.transform(content)
48
+ transform_proc = proc do |params_string, items_block, _third|
49
+ attributes = AttributeParser.parse(params_string.to_s.strip, CONTAINER_ATTRIBUTES)
50
+ heading_level = extract_heading_level(params_string)
51
+
52
+ attr_parts = ["appearance=\"#{normalize_appearance(attributes[:appearance])}\""]
53
+ attr_parts << "mode=\"#{attributes[:mode]}\"" if attributes[:mode]
54
+ attr_parts << "icon-placement=\"#{attributes[:icon_placement]}\"" if attributes[:icon_placement]
55
+ attr_parts << "heading-level=\"#{heading_level}\"" if heading_level
56
+
57
+ "<wa-accordion #{attr_parts.join(' ')}>#{build_items(items_block)}</wa-accordion>"
58
+ end
59
+
60
+ patterns = dual_syntax_patterns(PRIMARY_REGEX, ALTERNATIVE_REGEX, transform_proc)
61
+ apply_multiple_patterns(content, patterns)
62
+ end
63
+
64
+ def self.render_as_markdown(content, _options = {})
65
+ transform_proc = proc do |_params_string, items_block, _third|
66
+ items_block.scan(ITEM_REGEX).map do |header, body|
67
+ _flags, label = parse_item_flags_and_label(IconSlotParser.parse(header, ICON_SLOTS)[:remaining])
68
+ "### #{label}\n\n#{body.strip}"
69
+ end.join("\n\n")
70
+ end
71
+
72
+ patterns = dual_syntax_patterns(PRIMARY_REGEX, ALTERNATIVE_REGEX, transform_proc)
73
+ apply_multiple_patterns(content, patterns)
74
+ end
75
+
76
+ class << self
77
+ private
78
+
79
+ def build_items(items_block)
80
+ items_block.scan(ITEM_REGEX).map do |header, body|
81
+ icon_result = IconSlotParser.parse(header, ICON_SLOTS)
82
+ flags, label = parse_item_flags_and_label(icon_result[:remaining])
83
+
84
+ item_attrs = ["label=\"#{escape_html(label)}\""]
85
+ item_attrs << 'expanded' if flags.include?('expanded')
86
+ item_attrs << 'disabled' if flags.include?('disabled')
87
+
88
+ icon_html = IconSlotParser.to_html(icon_result[:icons], ICON_SLOTS[:slot_map])
89
+ body_html = markdown_to_html(body.strip)
90
+
91
+ "<wa-accordion-item #{item_attrs.join(' ')}>#{icon_html}#{body_html}</wa-accordion-item>"
92
+ end.join
93
+ end
94
+
95
+ # Consume leading expanded/disabled tokens; the remainder is the label.
96
+ def parse_item_flags_and_label(remaining)
97
+ tokens = remaining.to_s.strip.split(/\s+/)
98
+ flags = []
99
+ flags << tokens.shift while tokens.any? && ITEM_FLAGS.include?(tokens.first)
100
+ [flags, tokens.join(' ')]
101
+ end
102
+
103
+ def extract_heading_level(params_string)
104
+ return nil if params_string.nil? || params_string.strip.empty?
105
+
106
+ token = params_string.strip.split(/\s+/).find { |t| t.start_with?('heading:') }
107
+ return nil unless token
108
+
109
+ value = token.sub(/^heading:/, '')
110
+ return value if value == 'none' || value.match?(/\A[1-6]\z/)
111
+
112
+ nil
113
+ end
114
+
115
+ def normalize_appearance(appearance_param)
116
+ case appearance_param
117
+ when 'filled'
118
+ 'filled'
119
+ when 'filled-outlined'
120
+ 'filled-outlined'
121
+ when 'plain'
122
+ 'plain'
123
+ else
124
+ 'outlined'
125
+ end
126
+ end
127
+
128
+ def escape_html(text)
129
+ text.to_s
130
+ .gsub('&', '&amp;')
131
+ .gsub('<', '&lt;')
132
+ .gsub('>', '&gt;')
133
+ .gsub('"', '&quot;')
134
+ .gsub("'", '&#39;')
135
+ end
136
+ end
137
+ end
138
+ end
@@ -4,6 +4,7 @@
4
4
  # This file makes it easy to require all transformers at once
5
5
 
6
6
  require_relative 'transformers/base_transformer'
7
+ require_relative 'transformers/accordion_transformer'
7
8
  require_relative 'transformers/badge_transformer'
8
9
  require_relative 'transformers/button_transformer'
9
10
  require_relative 'transformers/callout_transformer'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Markawesome
4
- VERSION = '0.13.0'
4
+ VERSION = '0.14.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.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janne Waren
@@ -84,6 +84,7 @@ files:
84
84
  - lib/markawesome/plain_markdown_renderer.rb
85
85
  - lib/markawesome/transformer.rb
86
86
  - lib/markawesome/transformers.rb
87
+ - lib/markawesome/transformers/accordion_transformer.rb
87
88
  - lib/markawesome/transformers/badge_transformer.rb
88
89
  - lib/markawesome/transformers/base_transformer.rb
89
90
  - lib/markawesome/transformers/button_transformer.rb