From def0332708272876e2003dd59a4bd564a9bcea3c Mon Sep 17 00:00:00 2001 From: Clemens Lange Date: Tue, 28 Apr 2026 21:52:16 +0200 Subject: [PATCH] fix: pass through border-bottom for generated dropdown links --- README.md | 2 +- USAGE.md | 2 +- mkdocs_header_dropdown/plugin.py | 18 ++++++++- tests/test_plugin.py | 65 ++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 tests/test_plugin.py diff --git a/README.md b/README.md index 0a0b486..a30958a 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Each link in the `links` list supports: - `text` (string, required): The text displayed for the link - `url` (string, optional): The URL the link points to (not needed if using `submenu`) - `target` (string, optional): The target attribute (e.g., `_blank` for new tab) -- `bottom-border` (bool or int, optional): Add bottom divider of width 1px if true, or given value in px if number +- `border-bottom` (bool or int, optional): Add bottom divider of width 1px if true, or given value in px if number - `submenu` (list, optional): List of nested links for a submenu (see Nested Dropdowns below) ## Example: Using Shared Config File diff --git a/USAGE.md b/USAGE.md index 57029bf..d2b010d 100644 --- a/USAGE.md +++ b/USAGE.md @@ -108,7 +108,7 @@ Each link in the `links` list supports: | `text` | string | Yes | The link text | | `url` | string | Conditional | The target URL (can be relative or absolute). Required unless `submenu` is provided. Optional when using `submenu` to make the parent clickable. | | `target` | string | No | HTML target attribute (e.g., `_blank` for new tab) | -| `bottom-border` | bool or int | No | Add bottom divider of width 1px if true, or given value in px if number | +| `border-bottom` | bool or int | No | Add bottom divider of width 1px if true, or given value in px if number | | `submenu` | list | No | List of nested links (creates a submenu). See Nested Dropdowns below. | ## Advanced Examples diff --git a/mkdocs_header_dropdown/plugin.py b/mkdocs_header_dropdown/plugin.py index ff278c7..6f6e791 100644 --- a/mkdocs_header_dropdown/plugin.py +++ b/mkdocs_header_dropdown/plugin.py @@ -35,6 +35,11 @@ class HeaderDropdownPlugin(BasePlugin): ('dropdowns', config_options.Type(list, default=[])), ) + _AUTO_GENERATED_LINK_METADATA_KEYS = { + 'target', + 'border-bottom', + } + def _normalize_links(self, links): """ Normalize `border-bottom` value. Default width is 1px if specified @@ -60,6 +65,8 @@ def _normalize_links(self, links): if width>0: style += f"border-bottom: {width}px solid var(--md-default-fg-color--lightest);" item['extra_style'] = style + if isinstance(item.get('submenu'), list): + item['submenu'] = self._normalize_links(item['submenu']) normalized.append(item) return normalized @@ -84,11 +91,17 @@ def _generate_links_from_yaml(self, data, parent_key=None): if isinstance(data, dict): # Check for URL at this level parent_url = data.get('fallback') or data.get('url') + link_metadata = { + key: value + for key, value in data.items() + if key in self._AUTO_GENERATED_LINK_METADATA_KEYS + } + special_keys = {'fallback', 'url'} | self._AUTO_GENERATED_LINK_METADATA_KEYS # Collect submenu items from other keys submenu = [] for key, value in data.items(): - if key in ('fallback', 'url'): + if key in special_keys: continue # Skip these, used for parent URL if isinstance(value, str): @@ -103,7 +116,7 @@ def _generate_links_from_yaml(self, data, parent_key=None): # Nested dict - flatten it one level up # Don't create an intermediate item, just add its children directly for nested_key, nested_value in value.items(): - if nested_key in ('fallback', 'url'): + if nested_key in special_keys: continue if isinstance(nested_value, str): # Format display name for era-specific docs @@ -122,6 +135,7 @@ def _generate_links_from_yaml(self, data, parent_key=None): 'text': f"{parent_key}", 'target': '_blank' } + link.update(link_metadata) if parent_url: link['url'] = parent_url if submenu: diff --git a/tests/test_plugin.py b/tests/test_plugin.py new file mode 100644 index 0000000..105bc33 --- /dev/null +++ b/tests/test_plugin.py @@ -0,0 +1,65 @@ +import unittest + +from mkdocs_header_dropdown.plugin import HeaderDropdownPlugin + + +class AutoGeneratedLinksTest(unittest.TestCase): + def setUp(self): + self.plugin = HeaderDropdownPlugin() + + def test_auto_generated_parent_preserves_link_metadata(self): + link = self.plugin._generate_links_from_yaml( + { + 'fallback': 'https://example.com', + 'target': '_self', + 'border-bottom': True, + }, + 'Example', + ) + + self.assertEqual( + link, + { + 'text': 'Example', + 'target': '_self', + 'border-bottom': True, + 'url': 'https://example.com', + }, + ) + + def test_auto_generated_metadata_keys_do_not_create_submenu_items(self): + link = self.plugin._generate_links_from_yaml( + { + 'fallback': 'https://example.com', + 'target': '_self', + 'Run2': 'https://example.com/run2', + }, + 'Example', + ) + + self.assertEqual([item['text'] for item in link['submenu']], ['Run2']) + + def test_normalize_links_handles_submenu_links(self): + links = [ + { + 'text': 'Parent', + 'submenu': [ + { + 'text': 'Child', + 'url': 'https://example.com/child', + 'border-bottom': 2, + } + ], + } + ] + + normalized = self.plugin._normalize_links(links) + + self.assertIn( + 'border-bottom: 2px solid', + normalized[0]['submenu'][0]['extra_style'], + ) + + +if __name__ == '__main__': + unittest.main()