From 6224ac1197c2e617053bb11c6041958910c706b3 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Tue, 18 Jul 2023 20:27:15 +0200 Subject: [PATCH] Fixes #39 --- CHANGELOG.md | 6 ++ neoteroi/mkdocs/__init__.py | 2 +- neoteroi/mkdocs/cards/__init__.py | 25 +++++++-- neoteroi/mkdocs/cards/html.py | 23 ++++++-- neoteroi/mkdocs/markdown/processors.py | 5 ++ tests/test_cards.py | 77 ++++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3dd35..2a7d784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file. 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). +## [1.0.3] - 2023-07-18 :cat: + +- Adds support for icons in cards (by @Andy2003). +- Adds support for anchors with target="_blank" in cards. + ## [1.0.2] - 2023-04-25 + - Fixes detail in the `contribs` plugin: when the name is specified for a contributor by email address, it is used. - Improves `pyproject.toml`. diff --git a/neoteroi/mkdocs/__init__.py b/neoteroi/mkdocs/__init__.py index 7863915..976498a 100644 --- a/neoteroi/mkdocs/__init__.py +++ b/neoteroi/mkdocs/__init__.py @@ -1 +1 @@ -__version__ = "1.0.2" +__version__ = "1.0.3" diff --git a/neoteroi/mkdocs/cards/__init__.py b/neoteroi/mkdocs/cards/__init__.py index 3227960..c44210f 100644 --- a/neoteroi/mkdocs/cards/__init__.py +++ b/neoteroi/mkdocs/cards/__init__.py @@ -34,6 +34,11 @@ def build_html(self, parent, obj, props) -> None: if not isinstance(obj, list): raise TypeError("Expected a list of items describing cards.") + if self.root_config: + new_props = dict(**self.root_config) + new_props.update(props) + props = new_props + self.norm_obj(obj) builder = CardsHTMLBuilder(create_instance(CardsViewOptions, props)) builder.build_html(parent, Cards(create_instances(CardItem, obj))) @@ -55,18 +60,28 @@ class CardsSourceProcessor(BaseCardsProcessor, SourceBlockProcessor): class CardsExtension(Extension): """Extension that includes cards.""" - config = { - "priority": [12, "The priority to be configured for the extension."], - } + def __init__(self, *args, **kwargs): + self.config = { + "priority": [12, "The priority to be configured for the extension."], + "blank_target": [False, 'Whether to generate links with target="_blank"'], + } + super().__init__(*args, **kwargs) def extendMarkdown(self, md): md.registerExtension(self) priority = self.getConfig("priority") + configs = self.getConfigs() + del configs["priority"] + md.parser.blockprocessors.register( - CardsEmbeddedProcessor(md.parser), "cards", priority + 0.1 + CardsEmbeddedProcessor(md.parser).with_root_config(configs), + "cards", + priority + 0.1, ) md.parser.blockprocessors.register( - CardsSourceProcessor(md.parser), "cards-from-source", priority + CardsSourceProcessor(md.parser).with_root_config(configs), + "cards-from-source", + priority, ) diff --git a/neoteroi/mkdocs/cards/html.py b/neoteroi/mkdocs/cards/html.py index 98563b2..18e60be 100644 --- a/neoteroi/mkdocs/cards/html.py +++ b/neoteroi/mkdocs/cards/html.py @@ -2,8 +2,7 @@ import xml.etree.ElementTree as etree from dataclasses import dataclass -from neoteroi.mkdocs.markdown.images import build_icon_html -from neoteroi.mkdocs.markdown.images import build_image_html +from neoteroi.mkdocs.markdown.images import build_icon_html, build_image_html from .domain import CardItem, Cards @@ -16,6 +15,7 @@ class CardsViewOptions: class_name: str = "" cols: int = 3 image_bg: bool = False + blank_target: bool = False def __post_init__(self): if isinstance(self.cols, str): @@ -45,6 +45,14 @@ def _get_root_class(self): return base_class + " " + self.options.class_name return base_class + def _get_link_properties(self, item: CardItem): + assert item.url is not None + props = {"href": item.url} + + if self.options.blank_target: + props.update(target="_blank", rel="noopener") + return props + def build_html(self, parent, cards: Cards): root_element = etree.SubElement( parent, "div", {"class": self._get_root_class()} @@ -57,7 +65,9 @@ def build_item_html(self, parent, item: CardItem): item_element = etree.SubElement(parent, "div", self.get_item_props(item)) if item.url: - first_child = etree.SubElement(item_element, "a", {"href": item.url}) + first_child = etree.SubElement( + item_element, "a", self._get_link_properties(item) + ) else: first_child = etree.SubElement( item_element, "div", {"class": "nt-card-wrap"} @@ -68,9 +78,10 @@ def build_item_html(self, parent, item: CardItem): if item.image: self.build_image_html(wrapper_element, item) elif item.icon: - build_icon_html(etree.SubElement( - wrapper_element, "div", {"class": "nt-card-icon"} - ), item.icon) + build_icon_html( + etree.SubElement(wrapper_element, "div", {"class": "nt-card-icon"}), + item.icon, + ) text_wrapper = etree.SubElement( wrapper_element, "div", {"class": "nt-card-content"} diff --git a/neoteroi/mkdocs/markdown/processors.py b/neoteroi/mkdocs/markdown/processors.py index 6c27f0e..a6e271f 100644 --- a/neoteroi/mkdocs/markdown/processors.py +++ b/neoteroi/mkdocs/markdown/processors.py @@ -59,6 +59,7 @@ def pop_to_index(items, index): class BaseProcessor(ABC): + root_config: dict = {} parsers: Iterable[TextParser] = (YAMLParser(), JSONParser(), CSVParser()) @property @@ -143,6 +144,10 @@ def get_match(self, pattern, blocks) -> Optional[re.Match]: return match + def with_root_config(self, props): + self.__dict__["root_config"] = props + return self + class SourceBlockProcessor(BlockProcessor, BaseProcessor): """ diff --git a/tests/test_cards.py b/tests/test_cards.py index 5e09420..5af38d3 100644 --- a/tests/test_cards.py +++ b/tests/test_cards.py @@ -15,6 +15,15 @@ """ +EXAMPLE_1_b = """ +
+ + + + +
+""" + EXAMPLE_2 = """
@@ -114,6 +123,34 @@ def test_base_cards_processor_raises_for_not_list_input(): """, EXAMPLE_1, ], + [ + """ + ::cards:: flex=25 image-tags blank_target + + - title: Title A + url: /some-path/a + content: Lorem ipsum dolor sit amet 1. + image: /img/icons/lorem-ipsum-1.png + + - title: Title B + url: /some-path/b + content: Lorem ipsum dolor sit amet 2. + image: /img/icons/lorem-ipsum-2.png + + - title: Title C + url: /some-path/c + content: Lorem ipsum dolor sit amet 3. + image: /img/icons/lorem-ipsum-3.png + + - title: Title D + url: /some-path/d + content: Lorem ipsum dolor sit amet 4. + image: /img/icons/lorem-ipsum-4.png + + ::/cards:: + """, + EXAMPLE_1_b, + ], ], ) def test_cards_extension_image_tags(example, expected_result): @@ -121,6 +158,46 @@ def test_cards_extension_image_tags(example, expected_result): assert html.strip() == expected_result.strip() +@pytest.mark.parametrize( + "example,expected_result", + [ + [ + """ + ::cards:: flex=25 image-tags + + - title: Title A + url: /some-path/a + content: Lorem ipsum dolor sit amet 1. + image: /img/icons/lorem-ipsum-1.png + + - title: Title B + url: /some-path/b + content: Lorem ipsum dolor sit amet 2. + image: /img/icons/lorem-ipsum-2.png + + - title: Title C + url: /some-path/c + content: Lorem ipsum dolor sit amet 3. + image: /img/icons/lorem-ipsum-3.png + + - title: Title D + url: /some-path/d + content: Lorem ipsum dolor sit amet 4. + image: /img/icons/lorem-ipsum-4.png + + ::/cards:: + """, + EXAMPLE_1_b, + ], + ], +) +def test_cards_extension_target_blank_config(example, expected_result): + html = markdown.markdown( + example, extensions=[CardsExtension(priority=100, blank_target=True)] + ) + assert html.strip() == expected_result.strip() + + @pytest.mark.parametrize( "example,expected_result", [