Skip to content

Commit 6224ac1

Browse files
Fixes #39
1 parent cb94a30 commit 6224ac1

File tree

6 files changed

+126
-12
lines changed

6 files changed

+126
-12
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.3] - 2023-07-18 :cat:
9+
10+
- Adds support for icons in cards (by @Andy2003).
11+
- Adds support for anchors with target="_blank" in cards.
12+
813
## [1.0.2] - 2023-04-25
14+
915
- Fixes detail in the `contribs` plugin: when the name is specified for a
1016
contributor by email address, it is used.
1117
- Improves `pyproject.toml`.

neoteroi/mkdocs/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.2"
1+
__version__ = "1.0.3"

neoteroi/mkdocs/cards/__init__.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ def build_html(self, parent, obj, props) -> None:
3434
if not isinstance(obj, list):
3535
raise TypeError("Expected a list of items describing cards.")
3636

37+
if self.root_config:
38+
new_props = dict(**self.root_config)
39+
new_props.update(props)
40+
props = new_props
41+
3742
self.norm_obj(obj)
3843
builder = CardsHTMLBuilder(create_instance(CardsViewOptions, props))
3944
builder.build_html(parent, Cards(create_instances(CardItem, obj)))
@@ -55,18 +60,28 @@ class CardsSourceProcessor(BaseCardsProcessor, SourceBlockProcessor):
5560
class CardsExtension(Extension):
5661
"""Extension that includes cards."""
5762

58-
config = {
59-
"priority": [12, "The priority to be configured for the extension."],
60-
}
63+
def __init__(self, *args, **kwargs):
64+
self.config = {
65+
"priority": [12, "The priority to be configured for the extension."],
66+
"blank_target": [False, 'Whether to generate links with target="_blank"'],
67+
}
68+
super().__init__(*args, **kwargs)
6169

6270
def extendMarkdown(self, md):
6371
md.registerExtension(self)
6472
priority = self.getConfig("priority")
6573

74+
configs = self.getConfigs()
75+
del configs["priority"]
76+
6677
md.parser.blockprocessors.register(
67-
CardsEmbeddedProcessor(md.parser), "cards", priority + 0.1
78+
CardsEmbeddedProcessor(md.parser).with_root_config(configs),
79+
"cards",
80+
priority + 0.1,
6881
)
6982

7083
md.parser.blockprocessors.register(
71-
CardsSourceProcessor(md.parser), "cards-from-source", priority
84+
CardsSourceProcessor(md.parser).with_root_config(configs),
85+
"cards-from-source",
86+
priority,
7287
)

neoteroi/mkdocs/cards/html.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
import xml.etree.ElementTree as etree
33
from dataclasses import dataclass
44

5-
from neoteroi.mkdocs.markdown.images import build_icon_html
6-
from neoteroi.mkdocs.markdown.images import build_image_html
5+
from neoteroi.mkdocs.markdown.images import build_icon_html, build_image_html
76

87
from .domain import CardItem, Cards
98

@@ -16,6 +15,7 @@ class CardsViewOptions:
1615
class_name: str = ""
1716
cols: int = 3
1817
image_bg: bool = False
18+
blank_target: bool = False
1919

2020
def __post_init__(self):
2121
if isinstance(self.cols, str):
@@ -45,6 +45,14 @@ def _get_root_class(self):
4545
return base_class + " " + self.options.class_name
4646
return base_class
4747

48+
def _get_link_properties(self, item: CardItem):
49+
assert item.url is not None
50+
props = {"href": item.url}
51+
52+
if self.options.blank_target:
53+
props.update(target="_blank", rel="noopener")
54+
return props
55+
4856
def build_html(self, parent, cards: Cards):
4957
root_element = etree.SubElement(
5058
parent, "div", {"class": self._get_root_class()}
@@ -57,7 +65,9 @@ def build_item_html(self, parent, item: CardItem):
5765
item_element = etree.SubElement(parent, "div", self.get_item_props(item))
5866

5967
if item.url:
60-
first_child = etree.SubElement(item_element, "a", {"href": item.url})
68+
first_child = etree.SubElement(
69+
item_element, "a", self._get_link_properties(item)
70+
)
6171
else:
6272
first_child = etree.SubElement(
6373
item_element, "div", {"class": "nt-card-wrap"}
@@ -68,9 +78,10 @@ def build_item_html(self, parent, item: CardItem):
6878
if item.image:
6979
self.build_image_html(wrapper_element, item)
7080
elif item.icon:
71-
build_icon_html(etree.SubElement(
72-
wrapper_element, "div", {"class": "nt-card-icon"}
73-
), item.icon)
81+
build_icon_html(
82+
etree.SubElement(wrapper_element, "div", {"class": "nt-card-icon"}),
83+
item.icon,
84+
)
7485

7586
text_wrapper = etree.SubElement(
7687
wrapper_element, "div", {"class": "nt-card-content"}

neoteroi/mkdocs/markdown/processors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def pop_to_index(items, index):
5959

6060

6161
class BaseProcessor(ABC):
62+
root_config: dict = {}
6263
parsers: Iterable[TextParser] = (YAMLParser(), JSONParser(), CSVParser())
6364

6465
@property
@@ -143,6 +144,10 @@ def get_match(self, pattern, blocks) -> Optional[re.Match]:
143144

144145
return match
145146

147+
def with_root_config(self, props):
148+
self.__dict__["root_config"] = props
149+
return self
150+
146151

147152
class SourceBlockProcessor(BlockProcessor, BaseProcessor):
148153
"""

tests/test_cards.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
</div>
1616
"""
1717

18+
EXAMPLE_1_b = """
19+
<div class="nt-cards nt-grid cols-3">
20+
<div class="nt-card"><a href="/some-path/a" rel="noopener" target="_blank"><div><div class="nt-card-image tags"><img src="/img/icons/lorem-ipsum-1.png" /></div><div class="nt-card-content"><p class="nt-card-title">Title A</p><p class="nt-card-text">Lorem ipsum dolor sit amet 1.</p></div></div></a></div>
21+
<div class="nt-card"><a href="/some-path/b" rel="noopener" target="_blank"><div><div class="nt-card-image tags"><img src="/img/icons/lorem-ipsum-2.png" /></div><div class="nt-card-content"><p class="nt-card-title">Title B</p><p class="nt-card-text">Lorem ipsum dolor sit amet 2.</p></div></div></a></div>
22+
<div class="nt-card"><a href="/some-path/c" rel="noopener" target="_blank"><div><div class="nt-card-image tags"><img src="/img/icons/lorem-ipsum-3.png" /></div><div class="nt-card-content"><p class="nt-card-title">Title C</p><p class="nt-card-text">Lorem ipsum dolor sit amet 3.</p></div></div></a></div>
23+
<div class="nt-card"><a href="/some-path/d" rel="noopener" target="_blank"><div><div class="nt-card-image tags"><img src="/img/icons/lorem-ipsum-4.png" /></div><div class="nt-card-content"><p class="nt-card-title">Title D</p><p class="nt-card-text">Lorem ipsum dolor sit amet 4.</p></div></div></a></div>
24+
</div>
25+
"""
26+
1827
EXAMPLE_2 = """
1928
<div class="nt-cards nt-grid cols-3">
2029
<div class="nt-card"><a href="/some-path/a"><div><div class="nt-card-image tags"><img src="/img/icons/lorem-ipsum-1.png" /></div><div class="nt-card-content"><p class="nt-card-title">Title A</p><p class="nt-card-text">Lorem ipsum dolor sit amet 1.</p></div></div></a></div>
@@ -114,13 +123,81 @@ def test_base_cards_processor_raises_for_not_list_input():
114123
""",
115124
EXAMPLE_1,
116125
],
126+
[
127+
"""
128+
::cards:: flex=25 image-tags blank_target
129+
130+
- title: Title A
131+
url: /some-path/a
132+
content: Lorem ipsum dolor sit amet 1.
133+
image: /img/icons/lorem-ipsum-1.png
134+
135+
- title: Title B
136+
url: /some-path/b
137+
content: Lorem ipsum dolor sit amet 2.
138+
image: /img/icons/lorem-ipsum-2.png
139+
140+
- title: Title C
141+
url: /some-path/c
142+
content: Lorem ipsum dolor sit amet 3.
143+
image: /img/icons/lorem-ipsum-3.png
144+
145+
- title: Title D
146+
url: /some-path/d
147+
content: Lorem ipsum dolor sit amet 4.
148+
image: /img/icons/lorem-ipsum-4.png
149+
150+
::/cards::
151+
""",
152+
EXAMPLE_1_b,
153+
],
117154
],
118155
)
119156
def test_cards_extension_image_tags(example, expected_result):
120157
html = markdown.markdown(example, extensions=[CardsExtension(priority=100)])
121158
assert html.strip() == expected_result.strip()
122159

123160

161+
@pytest.mark.parametrize(
162+
"example,expected_result",
163+
[
164+
[
165+
"""
166+
::cards:: flex=25 image-tags
167+
168+
- title: Title A
169+
url: /some-path/a
170+
content: Lorem ipsum dolor sit amet 1.
171+
image: /img/icons/lorem-ipsum-1.png
172+
173+
- title: Title B
174+
url: /some-path/b
175+
content: Lorem ipsum dolor sit amet 2.
176+
image: /img/icons/lorem-ipsum-2.png
177+
178+
- title: Title C
179+
url: /some-path/c
180+
content: Lorem ipsum dolor sit amet 3.
181+
image: /img/icons/lorem-ipsum-3.png
182+
183+
- title: Title D
184+
url: /some-path/d
185+
content: Lorem ipsum dolor sit amet 4.
186+
image: /img/icons/lorem-ipsum-4.png
187+
188+
::/cards::
189+
""",
190+
EXAMPLE_1_b,
191+
],
192+
],
193+
)
194+
def test_cards_extension_target_blank_config(example, expected_result):
195+
html = markdown.markdown(
196+
example, extensions=[CardsExtension(priority=100, blank_target=True)]
197+
)
198+
assert html.strip() == expected_result.strip()
199+
200+
124201
@pytest.mark.parametrize(
125202
"example,expected_result",
126203
[

0 commit comments

Comments
 (0)