markawesome 0.17.0 → 0.18.1
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 +32 -0
- data/README.md +7 -0
- data/lib/markawesome/icon_attributes.rb +17 -7
- data/lib/markawesome/plain_markdown_renderer.rb +7 -2
- data/lib/markawesome/transformer.rb +11 -0
- data/lib/markawesome/transformers/card_transformer.rb +12 -2
- data/lib/markawesome/transformers/icon_transformer.rb +2 -1
- data/lib/markawesome/transformers/random_content_transformer.rb +151 -0
- data/lib/markawesome/transformers/tree_transformer.rb +163 -0
- data/lib/markawesome/transformers.rb +2 -0
- data/lib/markawesome/version.rb +1 -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: 6c2c5ebb3f9f231f78078d8ff504c309a0a20a2b786835db6763b79ec02c2ecb
|
|
4
|
+
data.tar.gz: c2828be808439565b2ff460feaf052ed97640fd9605a973897656d86b71160e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43d62e1980cd64700193e51dfef75007b4a64382ee70b4623283e24d29bd9dc6b0043834411bad243131abf57328587008704c858da90a4dc2d33a3410db9faf
|
|
7
|
+
data.tar.gz: 2668f52f02046b64911bbc7cb979fb4d9cd6c061eb811f3f06a7f21d0a46eb859adf300d836c30b1e544f2b03fabc39617a384c83c12ae70f1159db7b1424c02
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.18.1] - 2026-07-04
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Horizontal cards no longer overflow with multi-block bodies.** A `===horizontal` card whose body has more than one block (e.g. a `#` heading plus a paragraph) now wraps the body in a single `<div>`, so Web Awesome's `:host([orientation='horizontal']) .body slot::slotted(*) { height: 100% }` rule — which it applies to **every** direct body child — targets the one wrapper instead of each block. Previously each block was stretched to the full card height and the extra ones spilled out below the card. Vertical cards are unchanged (no wrapper is emitted). Parity locked by the new `card-horizontal-body-wrap` case in `markawesome-js`'s `test/parity-corpus.test.ts`.
|
|
14
|
+
|
|
15
|
+
## [0.18.0] - 2026-07-04
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`<wa-icon>` Web Awesome 3.10.0 attribute sync** — widens the shared icon vocabulary in `Markawesome::IconAttributes::SCHEMA` (used by both `$$$name` / `:::wa-icon …` **and**, transparently, callout icon overrides via `IconAttributes.pairs`, so the whole surface inherits it for free). Adds a **new `canvas` attribute** (`fixed`/`auto`/`square`/`roomy`; emitted only when specified — `fixed` is WA's default), a **new `semibold` variant** (alongside `thin light regular solid`), **nine new animations** (`flip-360`, `spin-snap`, `spin-snap-4`, `spin-snap-8`, `buzz`, `float`, `jello`, `swing`, `wag`), and a **full sync of the current WA family vocabulary** (`mosaic`, `pixel`, `vellum`, `slab-duo`, `slab-press-duo`, `chisel`, `etch`, `graphite`, `jelly`, `jelly-duo`, `jelly-fill`, `notdog`, `notdog-duo`, `slab`, `slab-press`, `thumbprint`, `utility`, `utility-duo`, `utility-fill`, `whiteboard` — family values are pure passthrough; whether one renders depends on the site's Font Awesome kit tier). The schema is a hard allowlist (`AttributeParser` silently drops unlisted tokens), so these values were previously unusable. **Deterministic emission order** is `family variant animation canvas` (`canvas` appended last) for byte-for-byte parity with `markawesome-js`. `auto-width` is unaffected — the engine never emitted it, and `canvas="auto"` is WA's supported replacement.
|
|
20
|
+
- New `RandomContentTransformer` producing Web Awesome's experimental `<wa-random-content>` — a container that shows one or more of its direct element children at random (optionally rotating them) and hides the rest, all in Web Awesome's own runtime. It gives an otherwise static site a "little bit dynamic" rotating tips / testimonials / featured-content block with **zero authored JavaScript**.
|
|
21
|
+
- **Primary syntax**: a `......` (6 dots) open fence carrying the params, options separated by a `>>>` line, closed by `......`. **Block alternative**: `:::wa-random-content <params>` … `>>>` … `:::`. The character space is nearly exhausted, so both glyphs are reused: `......` (six dots never occur in prose) and the **shared** `>>>` separator already used inside details/dialog/popover/tooltip.
|
|
22
|
+
- **Gated sigils**: `>>>` and `......` only act *inside* a `......`/`:::wa-random-content` block — a stray `>>>` or `...` in ordinary prose is inert. A line of exactly `>>>` or `......` inside an option is therefore a separator/close-fence; options cannot contain those literal lines (a hard constraint, like carousel's `~~~`).
|
|
23
|
+
- **Each option is wrapped in a single `<div>`** so Web Awesome sees exactly one selectable element child per option (a multi-paragraph option becomes one `<div>` with several `<p>` — precisely why the wrapper exists). Empty options are dropped (an empty rotating option would show nothing).
|
|
24
|
+
- **Params** (order-independent, on the opening fence line): `mode` (`unique` WA default / `random` / `sequence`, as a bare enum or `mode:VALUE`), `animation` (`none`/`fade`/`fade-up`/`fade-down`/`fade-left`/`fade-right`, bare or `animation:VALUE`), `autoplay` (bare boolean), `items` (a bare integer or `items:N`), and `autoplay-interval:N`. Bare enums/booleans are parsed by `AttributeParser`; `mode:`/`animation:` colon forms are enum-validated; unknown/invalid tokens are silently ignored. **Deterministic emission order** (for byte-parity with `markawesome-js`): `mode`, `items`, `animation`, `autoplay`, `autoplay-interval`. `mode`/`animation` are omitted when unspecified (WA supplies its own defaults).
|
|
25
|
+
- **Runs last in the pipeline** (after `TreeTransformer`): its options routinely wrap already-transformed components (callouts/badges/cards), and dialog/details/popover/tooltip must consume their own `>>>` upstream before our `^>>>$` split runs. Consequence: a `......` block is the outermost container and cannot be nested inside another container body (the same constraint `TreeTransformer` documents).
|
|
26
|
+
- **Plain-markdown degradation** (`render_as_markdown`, used for `.md` endpoints / llms.txt): the block degrades to its option bodies joined by blank lines, with the fences, params, and `>>>` separators dropped.
|
|
27
|
+
- New `TreeTransformer` producing Web Awesome's `<wa-tree>` / `<wa-tree-item>` from a **nested Markdown bullet list** — the first transformer that maps indentation (rather than flat item fences) onto a component. Ideal for file/directory layouts, taxonomies, and nested navigation.
|
|
28
|
+
- **Static-only scope**: Web Awesome's tree is fundamentally a *selection* control, and selection is interactive (needs JS). We deliberately emit a **display/navigation-only** tree — visual hierarchy via nesting, initial expand state, and leading folder/file icons, all declarative and static-safe. `selection`, `lazy`, `selected`, and selection events are skipped entirely.
|
|
29
|
+
- **Primary syntax**: a single `||||||` (6 pipes) open fence wrapping a normal nested Markdown list, closed by `||||||` (modeled on `LayoutTransformer`'s single-fence shape, not accordion's container+item fences). `|` is unused by any other transformer, and the transform runs before Markdown so there is no table collision. **Block alternative**: `:::wa-tree … :::`.
|
|
30
|
+
- **Fence token**: `open` (alias `expanded`) marks every branch node `expanded`. **Web Awesome runtime caveat** (verified against the WA 3.9.0 kit): `<wa-tree-item>` only honors a static `expanded` on items that are *visible at load*, so in practice `open` expands the **top-level** branches and deeper branches stay collapsed until their parent is opened (WA strips `expanded` from nested items at init). The attribute is still emitted on every branch on purpose — it's harmless (WA ignores the nested ones), records authorial intent, and is forward-compatible if WA ever honors nested initial-expand.
|
|
31
|
+
- **Per-node leading tokens** on each list item (stripped from the label, like the accordion item flags): `expanded` (force one branch open — subject to the same top-level-only WA caveat above) and `icon:name` (a leading content `<wa-icon name="name">` with no `slot`, e.g. `icon:folder`). The remaining text is the label.
|
|
32
|
+
- **Indentation parser**: list lines (`-`/`*`/`+`) are kept and their leading-whitespace width measured (a tab counts as four columns); nesting is built by comparing actual indent values, so 2-space and 4-space lists both work as long as children are strictly more indented than parents. Blank and non-list lines are skipped.
|
|
33
|
+
- **Labels are plain text** (HTML-escaped), so colon-bearing labels like `cbc:ID` and `<`/`>` are safe. `expanded` is emitted only on nodes that **have children** AND (the fence is `open` OR the node carries its own `expanded` flag); leaves never get `expanded`. Runs last in the pipeline (after accordion).
|
|
34
|
+
- **Plain-markdown degradation** (`render_as_markdown`, used for `.md` endpoints / llms.txt): the tree degrades to a clean nested bullet list (2-space indent per depth, tokens and fence stripped).
|
|
35
|
+
- Inline markdown / links in labels are a documented v1 follow-up; nesting a tree inside a markdown-converted body (accordion item, card) is not supported in v1 — the same pipeline constraint other components already have. Deep expand-on-load (beyond the top level) needs Web Awesome's interactive JS, so it is out of scope for the static surface.
|
|
36
|
+
|
|
37
|
+
### Documentation
|
|
38
|
+
|
|
39
|
+
- Documented the `<wa-tag with-remove>` static-site caveat in the README: the attribute renders a remove (×) button, but Web Awesome only *fires* a `wa-remove` event and does not remove the tag itself, so on a page with no authored JavaScript the button is inert. We **keep** the attribute (the emitted markup is valid Web Awesome) and document the one-line listener needed to make it functional, rather than dropping it from the static-safe surface. This also makes the existing 0.17.0 reference to "the `<wa-tag with-remove>` caveat" accurate.
|
|
40
|
+
|
|
9
41
|
## [0.17.0] - 2026-06-26
|
|
10
42
|
|
|
11
43
|
### Added
|
data/README.md
CHANGED
|
@@ -29,6 +29,13 @@ Used as the transformation engine for the [jekyll-webawesome](https://github.com
|
|
|
29
29
|
| **Tab Group** | `++++++` | `:::wa-tabs` | `<wa-tab-group><wa-tab>content</wa-tab></wa-tab-group>` |
|
|
30
30
|
| **Tag** | `@@@brand` | `:::wa-tag brand` | `<wa-tag variant="brand">content</wa-tag>` |
|
|
31
31
|
|
|
32
|
+
> **Note on `<wa-tag>` `with-remove`:** the `with-remove` attribute renders a
|
|
33
|
+
> remove (×) button, but Web Awesome only *fires* a `wa-remove` event — it does
|
|
34
|
+
> not remove the tag for you. On a page with no JavaScript beyond the Web Awesome
|
|
35
|
+
> library, the button is inert. To make it functional, add your own `wa-remove`
|
|
36
|
+
> listener (e.g. a single global handler that removes `event.target`). The markup
|
|
37
|
+
> we emit is valid either way.
|
|
38
|
+
|
|
32
39
|
## Layout Utilities
|
|
33
40
|
|
|
34
41
|
Layout utilities use `::::` (quadruple colon) syntax to wrap content in CSS layout containers. Inner content is not markdown-converted, so component `:::` syntax inside layouts works normally.
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Markawesome
|
|
4
|
-
# Shared <wa-icon> attribute vocabulary (family/variant/animation) and emission helper.
|
|
5
|
-
# Values verified against Web Awesome 3.
|
|
4
|
+
# Shared <wa-icon> attribute vocabulary (family/variant/animation/canvas) and emission helper.
|
|
5
|
+
# Values verified against Web Awesome 3.10.0. Adding a value later is a one-line change.
|
|
6
6
|
module IconAttributes
|
|
7
7
|
SCHEMA = {
|
|
8
|
-
family: %w[
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
family: %w[
|
|
9
|
+
classic sharp duotone sharp-duotone brands
|
|
10
|
+
mosaic pixel vellum slab-duo slab-press-duo chisel etch graphite
|
|
11
|
+
jelly jelly-duo jelly-fill notdog notdog-duo slab slab-press
|
|
12
|
+
thumbprint utility utility-duo utility-fill whiteboard
|
|
13
|
+
],
|
|
14
|
+
variant: %w[thin light regular solid semibold],
|
|
15
|
+
animation: %w[
|
|
16
|
+
beat fade beat-fade bounce flip shake spin spin-pulse spin-reverse
|
|
17
|
+
flip-360 spin-snap spin-snap-4 spin-snap-8 buzz float jello swing wag
|
|
18
|
+
],
|
|
19
|
+
canvas: %w[fixed auto square roomy]
|
|
11
20
|
}.freeze
|
|
12
21
|
|
|
13
|
-
# Returns ordered ['family="…"', 'variant="…"', 'animation="…"'] for present
|
|
22
|
+
# Returns ordered ['family="…"', 'variant="…"', 'animation="…"', 'canvas="…"'] for present
|
|
23
|
+
# keys. Emission order is fixed (canvas last) for byte-for-byte parity with markawesome-js.
|
|
14
24
|
def self.pairs(attributes)
|
|
15
|
-
%i[family variant animation].filter_map do |key|
|
|
25
|
+
%i[family variant animation canvas].filter_map do |key|
|
|
16
26
|
"#{key}=\"#{attributes[key]}\"" if attributes[key]
|
|
17
27
|
end
|
|
18
28
|
end
|
|
@@ -26,7 +26,8 @@ module Markawesome
|
|
|
26
26
|
#
|
|
27
27
|
# @param component [Symbol] one of :accordion, :callout, :badge, :button,
|
|
28
28
|
# :card, :carousel, :comparison, :copy_button, :details, :dialog, :icon,
|
|
29
|
-
# :image_dialog, :layout, :popover, :tabs, :tag,
|
|
29
|
+
# :image_dialog, :layout, :popover, :random_content, :tabs, :tag,
|
|
30
|
+
# :tooltip.
|
|
30
31
|
# @yield [content, options] Proc that receives the full source content
|
|
31
32
|
# and the renderer options; returns the content with that component
|
|
32
33
|
# syntax replaced.
|
|
@@ -62,6 +63,8 @@ module Markawesome
|
|
|
62
63
|
tag
|
|
63
64
|
tabs
|
|
64
65
|
accordion
|
|
66
|
+
tree
|
|
67
|
+
random_content
|
|
65
68
|
].freeze
|
|
66
69
|
|
|
67
70
|
TRANSFORMER_MAP = {
|
|
@@ -83,7 +86,9 @@ module Markawesome
|
|
|
83
86
|
icon: IconTransformer,
|
|
84
87
|
tag: TagTransformer,
|
|
85
88
|
tabs: TabsTransformer,
|
|
86
|
-
accordion: AccordionTransformer
|
|
89
|
+
accordion: AccordionTransformer,
|
|
90
|
+
tree: TreeTransformer,
|
|
91
|
+
random_content: RandomContentTransformer
|
|
87
92
|
}.freeze
|
|
88
93
|
|
|
89
94
|
def self.process(content, options = {})
|
|
@@ -39,6 +39,17 @@ module Markawesome
|
|
|
39
39
|
# components (same reason tabs runs near the end).
|
|
40
40
|
content = AccordionTransformer.transform(content)
|
|
41
41
|
|
|
42
|
+
# Tree runs after accordion; its body is a plain nested list (never a
|
|
43
|
+
# markdown-converted body), so ordering relative to accordion is moot.
|
|
44
|
+
content = TreeTransformer.transform(content)
|
|
45
|
+
|
|
46
|
+
# Random-content runs LAST: its options routinely wrap already-transformed
|
|
47
|
+
# components (callouts/badges/cards), and dialog/details/popover/tooltip
|
|
48
|
+
# must consume their own `>>>` upstream before our `^>>>$` item split
|
|
49
|
+
# runs. Consequence: a `......` block is the outermost container and cannot
|
|
50
|
+
# be nested inside another container body (same constraint Tree documents).
|
|
51
|
+
content = RandomContentTransformer.transform(content)
|
|
52
|
+
|
|
42
53
|
CodeBlockProtector.restore(content, tokens)
|
|
43
54
|
end
|
|
44
55
|
end
|
|
@@ -138,8 +138,7 @@ module Markawesome
|
|
|
138
138
|
|
|
139
139
|
# Main content
|
|
140
140
|
if parts[:content] && !parts[:content].empty?
|
|
141
|
-
|
|
142
|
-
html_parts << content_html
|
|
141
|
+
html_parts << body_content_html(parts[:content], orientation)
|
|
143
142
|
end
|
|
144
143
|
|
|
145
144
|
# Footer slot
|
|
@@ -149,6 +148,17 @@ module Markawesome
|
|
|
149
148
|
|
|
150
149
|
"<wa-card#{attr_string}>#{html_parts.join}</wa-card>"
|
|
151
150
|
end
|
|
151
|
+
|
|
152
|
+
# Horizontal cards wrap the body in a single <div> so Web Awesome's
|
|
153
|
+
# `:host([orientation='horizontal']) .body slot::slotted(*) { height: 100% }`
|
|
154
|
+
# rule — which it applies to EVERY direct body child — targets one wrapper
|
|
155
|
+
# instead of each block. Without the wrapper a multi-block body (e.g. a
|
|
156
|
+
# heading plus a paragraph) has each child stretched to the full card
|
|
157
|
+
# height, so the extra ones overflow below it.
|
|
158
|
+
def body_content_html(content, orientation)
|
|
159
|
+
content_html = markdown_to_html(content)
|
|
160
|
+
orientation == 'horizontal' ? "<div>#{content_html}</div>" : content_html
|
|
161
|
+
end
|
|
152
162
|
end
|
|
153
163
|
end
|
|
154
164
|
end
|
|
@@ -7,13 +7,14 @@ require_relative '../icon_attributes'
|
|
|
7
7
|
module Markawesome
|
|
8
8
|
# Transforms icon syntax into wa-icon elements
|
|
9
9
|
# Primary syntax: $$$icon-name (name only, decorative)
|
|
10
|
-
# Alternative syntax: :::wa-icon icon-name [family] [variant] [animation]\n[label]\n:::
|
|
10
|
+
# Alternative syntax: :::wa-icon icon-name [family] [variant] [animation] [canvas]\n[label]\n:::
|
|
11
11
|
#
|
|
12
12
|
# Examples:
|
|
13
13
|
# $$$settings -> <wa-icon name="settings"></wa-icon>
|
|
14
14
|
# $$$home -> <wa-icon name="home"></wa-icon>
|
|
15
15
|
# $$$user-circle -> <wa-icon name="user-circle"></wa-icon>
|
|
16
16
|
# :::wa-icon star spin\n::: -> <wa-icon name="star" animation="spin"></wa-icon>
|
|
17
|
+
# :::wa-icon star roomy\n::: -> <wa-icon name="star" canvas="roomy"></wa-icon>
|
|
17
18
|
# :::wa-icon heart solid\nFavorite\n::: ->
|
|
18
19
|
# <wa-icon name="heart" variant="solid" label="Favorite"></wa-icon>
|
|
19
20
|
class IconTransformer < BaseTransformer
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_transformer'
|
|
4
|
+
require_relative '../attribute_parser'
|
|
5
|
+
|
|
6
|
+
module Markawesome
|
|
7
|
+
# Transforms random-content syntax into Web Awesome's experimental
|
|
8
|
+
# <wa-random-content> element, which shows one or more of its direct element
|
|
9
|
+
# children at random (optionally rotating them) and hides the rest — all in
|
|
10
|
+
# WA's own runtime, so the author writes zero JavaScript. Ideal for a "little
|
|
11
|
+
# bit dynamic" rotating tips / testimonials / featured-content block on an
|
|
12
|
+
# otherwise static site.
|
|
13
|
+
#
|
|
14
|
+
# Primary syntax:
|
|
15
|
+
# ......mode:random items:2 animation:fade
|
|
16
|
+
# First option
|
|
17
|
+
# >>>
|
|
18
|
+
# Second option
|
|
19
|
+
# ......
|
|
20
|
+
# Alternative syntax:
|
|
21
|
+
# :::wa-random-content <params>
|
|
22
|
+
# First option
|
|
23
|
+
# >>>
|
|
24
|
+
# Second option
|
|
25
|
+
# :::
|
|
26
|
+
#
|
|
27
|
+
# Each option (split on a `>>>` line) has its rendered Markdown wrapped in a
|
|
28
|
+
# single <div> so WA sees exactly one selectable element child per option.
|
|
29
|
+
#
|
|
30
|
+
# Params (order-independent, on the opening fence line):
|
|
31
|
+
# - mode: unique (WA default) | random | sequence — bare enum or mode:VALUE
|
|
32
|
+
# - animation: none | fade | fade-up | fade-down | fade-left | fade-right —
|
|
33
|
+
# bare enum or animation:VALUE
|
|
34
|
+
# - autoplay: bare boolean flag
|
|
35
|
+
# - items: a bare integer or items:N (how many children to show)
|
|
36
|
+
# - autoplay-interval:N (ms between rotations)
|
|
37
|
+
# Unknown/invalid tokens are silently ignored. Deterministic emission order:
|
|
38
|
+
# mode, items, animation, autoplay, autoplay-interval.
|
|
39
|
+
#
|
|
40
|
+
# The `>>>` separator is the SHARED glyph already used inside
|
|
41
|
+
# details/dialog/popover/tooltip; it only acts as an item separator *inside* a
|
|
42
|
+
# `......`/`:::wa-random-content` block. A line of exactly `>>>` or `......`
|
|
43
|
+
# inside an option is therefore a separator/close-fence — options cannot
|
|
44
|
+
# contain those literal lines (a hard constraint, like carousel's `~~~`).
|
|
45
|
+
class RandomContentTransformer < BaseTransformer
|
|
46
|
+
COMPONENT_ATTRIBUTES = {
|
|
47
|
+
mode: %w[unique random sequence],
|
|
48
|
+
animation: %w[none fade fade-up fade-down fade-left fade-right],
|
|
49
|
+
autoplay: %w[autoplay]
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
# Dots MUST be escaped. The body is unstructured `(.*?)` under /m, so the
|
|
53
|
+
# closing anchor makes an unclosed fence not match and lets multiple blocks
|
|
54
|
+
# match left-to-right. Deliberately NO `\s*` after the alt fence (unlike
|
|
55
|
+
# carousel): with an unstructured body a greedy `\s*` would swallow the
|
|
56
|
+
# first newline and capture the first option line as params. `([^\n]*)`
|
|
57
|
+
# only — a leading space on the alt form is stripped by the parser.
|
|
58
|
+
PRIMARY_REGEX = /^\.{6}([^\n]*)\n(.*?)\n\.{6}/m
|
|
59
|
+
ALTERNATIVE_REGEX = /^:::wa-random-content([^\n]*)\n(.*?)\n:::/m
|
|
60
|
+
ITEM_SEPARATOR = /^>>>$/
|
|
61
|
+
|
|
62
|
+
def self.transform(content)
|
|
63
|
+
transform_proc = proc do |params, items_block|
|
|
64
|
+
build_html(extract_items(items_block), parse_params(params))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
patterns = dual_syntax_patterns(PRIMARY_REGEX, ALTERNATIVE_REGEX, transform_proc)
|
|
68
|
+
apply_multiple_patterns(content, patterns)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Degrade to the option bodies joined by blank lines (fences, params, and
|
|
72
|
+
# `>>>` separators dropped), used for `.md` endpoints / llms.txt.
|
|
73
|
+
def self.render_as_markdown(content, _options = {})
|
|
74
|
+
transform_proc = proc do |_params, items_block|
|
|
75
|
+
extract_items(items_block).reject(&:empty?).join("\n\n")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
patterns = dual_syntax_patterns(PRIMARY_REGEX, ALTERNATIVE_REGEX, transform_proc)
|
|
79
|
+
apply_multiple_patterns(content, patterns)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class << self
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def extract_items(items_block)
|
|
86
|
+
items_block.split(ITEM_SEPARATOR).map(&:strip)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse_params(params)
|
|
90
|
+
return {} if params.nil? || params.strip.empty?
|
|
91
|
+
|
|
92
|
+
result = {}
|
|
93
|
+
|
|
94
|
+
# Bare enum/boolean tokens via AttributeParser (rightmost-wins).
|
|
95
|
+
attributes = AttributeParser.parse(params, COMPONENT_ATTRIBUTES)
|
|
96
|
+
result['mode'] = attributes[:mode] if attributes[:mode]
|
|
97
|
+
result['animation'] = attributes[:animation] if attributes[:animation]
|
|
98
|
+
result['autoplay'] = true if attributes[:autoplay]
|
|
99
|
+
|
|
100
|
+
# Manual pass for key:value and bare-integer tokens.
|
|
101
|
+
params.strip.split(/\s+/).each { |token| merge_manual_token(result, token) }
|
|
102
|
+
|
|
103
|
+
result
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Fold a single space-separated token into the parsed attribute hash:
|
|
107
|
+
# `mode:`/`animation:` (enum-validated), `items:` / bare integer, and
|
|
108
|
+
# `autoplay-interval:`. Anything else is ignored.
|
|
109
|
+
def merge_manual_token(result, token)
|
|
110
|
+
if token.include?(':')
|
|
111
|
+
key, value = token.split(':', 2)
|
|
112
|
+
case key
|
|
113
|
+
when 'mode'
|
|
114
|
+
result['mode'] = value if COMPONENT_ATTRIBUTES[:mode].include?(value)
|
|
115
|
+
when 'animation'
|
|
116
|
+
result['animation'] = value if COMPONENT_ATTRIBUTES[:animation].include?(value)
|
|
117
|
+
when 'items'
|
|
118
|
+
result['items'] = value
|
|
119
|
+
when 'autoplay-interval'
|
|
120
|
+
result['autoplay-interval'] = value
|
|
121
|
+
end
|
|
122
|
+
elsif token.match?(/^\d+$/)
|
|
123
|
+
result['items'] = token
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def build_html(items, params)
|
|
128
|
+
attr_string = build_attributes(params)
|
|
129
|
+
|
|
130
|
+
# Empty options are dropped — an empty rotating option would show
|
|
131
|
+
# nothing. Keep Kramdown's trailing "\n" inside each <div> (no strip).
|
|
132
|
+
divs = items.reject(&:empty?).map { |item| "<div>#{markdown_to_html(item)}</div>" }.join
|
|
133
|
+
|
|
134
|
+
"<wa-random-content#{attr_string}>#{divs}</wa-random-content>"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Fixed emission order for byte-parity: mode, items, animation, autoplay,
|
|
138
|
+
# autoplay-interval.
|
|
139
|
+
def build_attributes(params)
|
|
140
|
+
attrs = []
|
|
141
|
+
attrs << "mode=\"#{params['mode']}\"" if params['mode']
|
|
142
|
+
attrs << "items=\"#{params['items']}\"" if params['items']
|
|
143
|
+
attrs << "animation=\"#{params['animation']}\"" if params['animation']
|
|
144
|
+
attrs << 'autoplay' if params['autoplay']
|
|
145
|
+
attrs << "autoplay-interval=\"#{params['autoplay-interval']}\"" if params['autoplay-interval']
|
|
146
|
+
|
|
147
|
+
attrs.empty? ? '' : " #{attrs.join(' ')}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_transformer'
|
|
4
|
+
require_relative '../icon_slot_parser'
|
|
5
|
+
|
|
6
|
+
module Markawesome
|
|
7
|
+
# Transforms tree syntax into wa-tree / wa-tree-item elements from a nested
|
|
8
|
+
# Markdown bullet list.
|
|
9
|
+
# Primary syntax:
|
|
10
|
+
# ||||||open?
|
|
11
|
+
# - icon:folder? expanded? Label
|
|
12
|
+
# - child label
|
|
13
|
+
# ||||||
|
|
14
|
+
# Alternative syntax: :::wa-tree open? ...same list... :::
|
|
15
|
+
#
|
|
16
|
+
# Web Awesome's tree is fundamentally a *selection* control, which is
|
|
17
|
+
# interactive (needs JS). On a static site we deliberately emit a
|
|
18
|
+
# display/navigation-only tree: visual hierarchy via nesting, initial expand
|
|
19
|
+
# state, and leading folder/file icons — all declarative and static-safe. We
|
|
20
|
+
# skip selection, lazy, selected, and selection events entirely.
|
|
21
|
+
#
|
|
22
|
+
# Fence token:
|
|
23
|
+
# - open (alias expanded) -> mark every branch (node with children) expanded
|
|
24
|
+
# Per-node leading tokens (stripped from the label):
|
|
25
|
+
# - expanded -> force this one branch open
|
|
26
|
+
# - icon:name -> leading content <wa-icon name="name"> on the item
|
|
27
|
+
# The remaining text is the (plain-text, HTML-escaped) label.
|
|
28
|
+
#
|
|
29
|
+
# expanded is emitted only on nodes that HAVE children AND (fence open OR the
|
|
30
|
+
# node's own expanded flag); leaves never get expanded.
|
|
31
|
+
#
|
|
32
|
+
# WA runtime caveat (verified against the WA 3.9.0 kit): <wa-tree-item> only
|
|
33
|
+
# honors a static `expanded` on items that are VISIBLE at load, so in practice
|
|
34
|
+
# `open` expands the TOP-LEVEL branches and deeper branches stay collapsed
|
|
35
|
+
# until their parent is opened (WA strips `expanded` from nested items at
|
|
36
|
+
# init). We still emit `expanded` on every branch on purpose: it's harmless
|
|
37
|
+
# (WA ignores the nested ones), records authorial intent, and is
|
|
38
|
+
# forward-compatible if WA ever honors nested initial-expand.
|
|
39
|
+
class TreeTransformer < BaseTransformer
|
|
40
|
+
# Content slot: emits <wa-icon name="..."> with no slot attribute.
|
|
41
|
+
ICON_SLOTS = {
|
|
42
|
+
default: 'content',
|
|
43
|
+
slots: %w[content],
|
|
44
|
+
slot_map: { 'content' => 'content' }
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
ITEM_FLAGS = %w[expanded].freeze
|
|
48
|
+
FENCE_TOKENS = %w[open expanded].freeze
|
|
49
|
+
TAB_WIDTH = 4
|
|
50
|
+
|
|
51
|
+
PRIMARY_REGEX = /^\|{6}[ \t]*([^\n]*)\n(.*?)\n\|{6}/m
|
|
52
|
+
ALTERNATIVE_REGEX = /^:::wa-tree[ \t]*([^\n]*)\n(.*?)\n:::/m
|
|
53
|
+
LIST_LINE_REGEX = /^(\s*)[-*+]\s+(.*)$/
|
|
54
|
+
|
|
55
|
+
def self.transform(content)
|
|
56
|
+
transform_proc = proc do |params_string, body, _third|
|
|
57
|
+
fence_open = fence_open?(params_string)
|
|
58
|
+
nodes = parse_lines(body)
|
|
59
|
+
"<wa-tree>#{build_items(nodes, fence_open)}</wa-tree>"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
patterns = dual_syntax_patterns(PRIMARY_REGEX, ALTERNATIVE_REGEX, transform_proc)
|
|
63
|
+
apply_multiple_patterns(content, patterns)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Degrade to a clean nested Markdown list (2-space indent per depth, tokens
|
|
67
|
+
# stripped, fence removed).
|
|
68
|
+
def self.render_as_markdown(content, _options = {})
|
|
69
|
+
transform_proc = proc do |_params_string, body, _third|
|
|
70
|
+
nodes = parse_lines(body)
|
|
71
|
+
render_list(nodes, -1, { pos: 0 }, 0)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
patterns = dual_syntax_patterns(PRIMARY_REGEX, ALTERNATIVE_REGEX, transform_proc)
|
|
75
|
+
apply_multiple_patterns(content, patterns)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class << self
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def fence_open?(params_string)
|
|
82
|
+
params_string.to_s.strip.split(/\s+/).any? { |t| FENCE_TOKENS.include?(t) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Keep only list lines; record each line's indent width and its raw text
|
|
86
|
+
# (everything after the bullet). Blank/non-list lines are skipped.
|
|
87
|
+
def parse_lines(body)
|
|
88
|
+
nodes = []
|
|
89
|
+
body.to_s.each_line do |line|
|
|
90
|
+
match = LIST_LINE_REGEX.match(line.chomp)
|
|
91
|
+
next unless match
|
|
92
|
+
|
|
93
|
+
nodes << { indent: indent_width(match[1]), raw: match[2] }
|
|
94
|
+
end
|
|
95
|
+
nodes
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def indent_width(whitespace)
|
|
99
|
+
whitespace.to_s.chars.sum { |char| char == "\t" ? TAB_WIDTH : 1 }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_items(nodes, fence_open)
|
|
103
|
+
helper(nodes, -1, { pos: 0 }, fence_open)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Pointer + recursion comparing actual indent values (width-agnostic, no
|
|
107
|
+
# fixed step assumed): children are strictly more indented than the parent.
|
|
108
|
+
def helper(nodes, parent_indent, state, fence_open)
|
|
109
|
+
out = +''
|
|
110
|
+
while state[:pos] < nodes.length && nodes[state[:pos]][:indent] > parent_indent
|
|
111
|
+
node = nodes[state[:pos]]
|
|
112
|
+
current = node[:indent]
|
|
113
|
+
state[:pos] += 1
|
|
114
|
+
children_html = helper(nodes, current, state, fence_open)
|
|
115
|
+
out << emit_item(node, children_html, fence_open)
|
|
116
|
+
end
|
|
117
|
+
out
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def emit_item(node, children_html, fence_open)
|
|
121
|
+
icon_result = IconSlotParser.parse(node[:raw], ICON_SLOTS)
|
|
122
|
+
flags, label = parse_item_flags_and_label(icon_result[:remaining])
|
|
123
|
+
has_children = !children_html.empty?
|
|
124
|
+
expanded = has_children && (fence_open || flags.include?('expanded'))
|
|
125
|
+
|
|
126
|
+
attrs = expanded ? ' expanded' : ''
|
|
127
|
+
icon_html = IconSlotParser.to_html(icon_result[:icons], ICON_SLOTS[:slot_map])
|
|
128
|
+
"<wa-tree-item#{attrs}>#{icon_html}#{escape_html(label)}#{children_html}</wa-tree-item>"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def render_list(nodes, parent_indent, state, depth)
|
|
132
|
+
lines = []
|
|
133
|
+
while state[:pos] < nodes.length && nodes[state[:pos]][:indent] > parent_indent
|
|
134
|
+
node = nodes[state[:pos]]
|
|
135
|
+
current = node[:indent]
|
|
136
|
+
state[:pos] += 1
|
|
137
|
+
_flags, label = parse_item_flags_and_label(IconSlotParser.parse(node[:raw], ICON_SLOTS)[:remaining])
|
|
138
|
+
lines << "#{' ' * depth}- #{label}"
|
|
139
|
+
child = render_list(nodes, current, state, depth + 1)
|
|
140
|
+
lines << child unless child.empty?
|
|
141
|
+
end
|
|
142
|
+
lines.join("\n")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Consume leading expanded tokens; the remainder is the label.
|
|
146
|
+
def parse_item_flags_and_label(remaining)
|
|
147
|
+
tokens = remaining.to_s.strip.split(/\s+/)
|
|
148
|
+
flags = []
|
|
149
|
+
flags << tokens.shift while tokens.any? && ITEM_FLAGS.include?(tokens.first)
|
|
150
|
+
[flags, tokens.join(' ')]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def escape_html(text)
|
|
154
|
+
text.to_s
|
|
155
|
+
.gsub('&', '&')
|
|
156
|
+
.gsub('<', '<')
|
|
157
|
+
.gsub('>', '>')
|
|
158
|
+
.gsub('"', '"')
|
|
159
|
+
.gsub("'", ''')
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -19,7 +19,9 @@ require_relative 'transformers/icon_transformer'
|
|
|
19
19
|
require_relative 'transformers/image_dialog_transformer'
|
|
20
20
|
require_relative 'transformers/layout_transformer'
|
|
21
21
|
require_relative 'transformers/popover_transformer'
|
|
22
|
+
require_relative 'transformers/random_content_transformer'
|
|
22
23
|
require_relative 'transformers/tabs_transformer'
|
|
23
24
|
require_relative 'transformers/tag_transformer'
|
|
24
25
|
require_relative 'transformers/tooltip_transformer'
|
|
26
|
+
require_relative 'transformers/tree_transformer'
|
|
25
27
|
require_relative 'transformers/video_transformer'
|
data/lib/markawesome/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.18.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Janne Waren
|
|
@@ -100,9 +100,11 @@ files:
|
|
|
100
100
|
- lib/markawesome/transformers/image_dialog_transformer.rb
|
|
101
101
|
- lib/markawesome/transformers/layout_transformer.rb
|
|
102
102
|
- lib/markawesome/transformers/popover_transformer.rb
|
|
103
|
+
- lib/markawesome/transformers/random_content_transformer.rb
|
|
103
104
|
- lib/markawesome/transformers/tabs_transformer.rb
|
|
104
105
|
- lib/markawesome/transformers/tag_transformer.rb
|
|
105
106
|
- lib/markawesome/transformers/tooltip_transformer.rb
|
|
107
|
+
- lib/markawesome/transformers/tree_transformer.rb
|
|
106
108
|
- lib/markawesome/transformers/video_transformer.rb
|
|
107
109
|
- lib/markawesome/version.rb
|
|
108
110
|
- markawesome.gemspec
|