Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 16 additions & 2 deletions mkdocs_header_dropdown/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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:
Expand Down
65 changes: 65 additions & 0 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
@@ -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()
Loading