{# Content Components Reusable macro-based content display components for the Bengal default theme. Components: - content_tiles(...): Flexible content display with variants (recommended) - article_card(article, show_excerpt=True, show_image=False): Rich article preview card - child_page_tiles(posts, subsections): [DEPRECATED] Use content_tiles() instead - tag_list(tags, small=False, linkable=True): Styled tag badges - popular_tags(limit=10): Tag cloud widget with popular tags - random_posts(count=3): Random post discovery widget Usage: from 'partials/content-components.html' import content_tiles, article_card, tag_list New recommended API: content_tiles(title="In This Section", children=posts, subsections=subsections) content_tiles(title="Related", related=page.related_posts[:3], variant='minimal') content_tiles(children=posts, related=True, group_by='type') Legacy (still works): article_card(post, show_image=True) tag_list(page.tags, small=True) #} {# Article Card Component Displays a rich card preview of an article with metadata, tags, and optional image. Args: article: Page object to display (required) show_excerpt: Boolean (default: True) - show description/excerpt show_image: Boolean (default: False) - show featured image Example: {{ article_card(post, show_image=True, show_excerpt=True) }} #} {% macro article_card(article, show_excerpt=True, show_image=False) %}
{% if show_image and article.metadata.get('image') %} {# Responsive image with srcset - dogfooding image_srcset() #} {{ article.metadata.get('image') | image_alt }} {% endif %}
{# Tag badges - dogfooding has_tag() #}
{% if article | has_tag('featured') %} ⭐ Featured {% endif %} {% if article | has_tag('tutorial') %} 📚 Tutorial {% endif %} {% if article | has_tag('new') %} ✨ New {% endif %}

{{ article.title }}

{% if article.date %} {% endif %} {% if article.metadata.get('author') and 'content.author' in site.theme_config.features %} {{ article.metadata.get('author') }} {% endif %} {% if article.content and 'content.reading_time' in site.theme_config.features %} {{ article.content | reading_time }} min read {% endif %}
{% if show_excerpt and 'content.excerpts' in site.theme_config.features %}

{% if article.metadata.get('description') %} {{ article.metadata.get('description') }} {% elif article.content %} {{ article.content | strip_html | excerpt(150) }} {% endif %}

{% endif %} {% if article.tags %}
{{ tag_list(article.tags, small=True) }}
{% endif %}
{% endmacro %} {# Content Tiles Component (Enhanced) Flexible content display with optional headers, multiple content sources, and variant display modes. Successor to child_page_tiles. Args: title: Header for children section (string, or None to suppress) children: List of child pages to display subsections: List of child sections to display related: Related pages (list, or True to auto-get from page.related_posts) related_title: Header for related section (string, or None to suppress) related_limit: Max related pages when related=True (default: 5) variant: Display style - 'compact' (rows) | 'cards' | 'minimal' show_descriptions: Show item descriptions (default: True) show_icons: Show item icons (default: True) group_by: Group items - None | 'type' (sections first, then pages, then related) Variants: - compact: Row-based list with icons (default, current style) - cards: Grid of preview cards - minimal: Simple link list, no descriptions Example: {{ content_tiles(title="In This Section", children=posts, subsections=subsections) }} {{ content_tiles(related=page.related_posts[:3], related_title="Related", variant='minimal') }} {{ content_tiles(title="In This Section", children=posts, related=True, related_title="Related Pages", group_by='type') }} #} {% macro content_tiles( title=None, children=None, subsections=None, related=None, related_title=None, related_limit=5, variant='compact', show_descriptions=True, show_icons=True, group_by=None ) %} {% set all_items = [] %} {# Add subsections with metadata #} {% if subsections %} {% for subsection in subsections %} {% set _ = all_items.append({ 'type': 'section', 'group': 'children', 'obj': subsection, 'title': subsection.title, 'href': subsection.href, 'description': subsection.metadata.get('description', '') if subsection.metadata and subsection.metadata.get('description', '') else '', 'weight': subsection.metadata.get('weight', 999999) if subsection.metadata else 999999, 'page_count': subsection.pages | length if subsection.pages else 0, 'icon': subsection.metadata.get('icon', '') if subsection.metadata else '' }) %} {% endfor %} {% endif %} {# Add child pages with metadata #} {% if children %} {% for post in children %} {# Skip the current page if it appears in child posts #} {% if not ((page is defined and page) and (post._path == page._path)) %} {% set _ = all_items.append({ 'type': 'page', 'group': 'children', 'obj': post, 'title': post.title, 'href': post.href, 'description': post.metadata.get('description', '') if post.metadata and post.metadata.get('description', '') else (post.content | strip_html | excerpt(120) if post.content else ''), 'weight': post.metadata.get('weight', 999999) if post.metadata else 999999, 'date': post.date if post.date else none, 'icon': post.metadata.get('icon', '') if post.metadata else '' }) %} {% endif %} {% endfor %} {% endif %} {# Add related pages (deduped - won't repeat pages from children) #} {% set related_pages = [] %} {% if related is sameas true %} {# Auto-get from page.related_posts #} {% if page is defined and page and page.related_posts %} {% set related_pages = page.related_posts[:related_limit] %} {% endif %} {% elif related %} {% set related_pages = related %} {% endif %} {# Build a set of URLs already in children for O(1) lookup #} {% set children_urls = [] %} {% for item in all_items %} {% set _ = children_urls.append(item.href) %} {% endfor %} {% for rel_page in related_pages %} {# Skip current page and any pages already in children #} {% set is_current = (page is defined and page) and (rel_page._path == page._path) %} {% set is_duplicate = rel_page.href in children_urls %} {% if not is_current and not is_duplicate %} {% set _ = all_items.append({ 'type': 'related', 'group': 'related', 'obj': rel_page, 'title': rel_page.title, 'href': rel_page.href, 'description': rel_page.metadata.get('description', '') if rel_page.metadata and rel_page.metadata.get('description', '') else (rel_page.content | strip_html | excerpt(100) if rel_page.content else ''), 'weight': 999999, 'date': rel_page.date if rel_page.date else none, 'icon': rel_page.metadata.get('icon', '') if rel_page.metadata else '' }) %} {% endif %} {% endfor %} {% if all_items %} {# Sort items: by group (children first), then weight, then title #} {% if group_by == 'type' %} {% set sorted_items = all_items | sort(attribute='group,weight,title') %} {% else %} {% set sorted_items = all_items | sort(attribute='weight,title') %} {% endif %}
{% if title %}

{{ title }}

{% endif %} {% if variant == 'cards' %} {# Card grid variant #}
{% for item in sorted_items %}
{% if show_icons %}
{% set rendered_icon = icon(item.icon, size=24) if item.icon else '' %} {% if rendered_icon %}{{ rendered_icon }} {% elif item.type == 'section' %}{{ icon('folder', size=24) }} {% elif item.type == 'related' %}{{ icon('link', size=24) }} {% else %}{{ icon('file-text', size=24) }}{% endif %}
{% endif %}

{{ item.title }}

{% if show_descriptions and item.description %}

{{ item.description | truncate(120) }}

{% endif %}
{% if item.type == 'section' and item.page_count > 0 %} {{ item.page_count }} page{{ 's' if item.page_count != 1 }} {% elif item.type == 'related' %} Related {% elif 'date' in item and item['date'] %} {{ item['date'] | time_ago }} {% endif %}
{% endfor %}
{% elif variant == 'minimal' %} {# Minimal list variant #} {% else %} {# Compact (default) variant - row-based list #} {% set current_group = namespace(value='') %}
{% for item in sorted_items %} {# Optional group headers when group_by='type' #} {% if group_by == 'type' and item.group != current_group.value %} {% set current_group.value = item.group %} {% if item.group == 'related' and related_title %}
{{ related_title }}
{% endif %} {% endif %}
{% if show_icons %}
{% set rendered_icon = icon(item.icon, size=20) if item.icon else '' %} {% if rendered_icon %}{{ rendered_icon }} {% elif item.type == 'section' %}{{ icon('folder', size=20) }} {% elif item.type == 'related' %}{{ icon('link', size=20) }} {% else %}{{ icon('file-text', size=20) }}{% endif %}
{% endif %}
{{ item.title }} {% if show_descriptions and item.description %} {{ item.description }} {% endif %} {% if item.type == 'section' and item.page_count > 0 %} {{ item.page_count }} page{{ 's' if item.page_count != 1 }} {% elif item.type == 'related' %} Related {% elif 'date' in item and item['date'] %} {{ item['date'] | time_ago }} {% endif %}
{% endfor %}
{% endif %}
{% endif %} {% endmacro %} {# Child Page Tiles Component (Legacy) DEPRECATED: Use content_tiles() instead for new implementations. Kept for backward compatibility. Displays subsections and child pages as compact row-based items with icons. Args: posts: List of child pages to display (optional, defaults to None) subsections: List of child sections to display (optional, defaults to None) Example: {{ child_page_tiles(posts=page.regular_pages, subsections=page.sections) }} #} {% macro child_page_tiles(posts=None, subsections=None) %} {% if posts or subsections %} {# Merge subsections and pages into unified list #} {% set all_items = [] %} {# Add subsections with metadata #} {% if subsections %} {% for subsection in subsections %} {% set _ = all_items.append({ 'type': 'section', 'obj': subsection, 'title': subsection.title, 'href': subsection.href, 'description': subsection.metadata.get('description', '') if subsection.metadata and subsection.metadata.get('description', '') else '', 'weight': subsection.metadata.get('weight', 999999) if subsection.metadata else 999999, 'page_count': subsection.pages | length if subsection.pages else 0, 'icon': subsection.metadata.get('icon', '') if subsection.metadata else '' }) %} {% endfor %} {% endif %} {# Add pages with metadata #} {% if posts %} {% for post in posts %} {# Skip the current section index page if it appears in child posts #} {% if not ((page is defined and page) and (post._path == page._path)) %} {% set _ = all_items.append({ 'type': 'page', 'obj': post, 'title': post.title, 'href': post.href, 'description': post.metadata.get('description', '') if post.metadata and post.metadata.get('description', '') else (post.content | strip_html | excerpt(120) if post.content else ''), 'weight': post.metadata.get('weight', 999999) if post.metadata else 999999, 'date': post.date if post.date else none, 'icon': post.metadata.get('icon', '') if post.metadata else '' }) %} {% endif %} {% endfor %} {% endif %} {# Sort by weight (ascending), then title #} {% set sorted_items = all_items | sort(attribute='weight,title') %}
{% for item in sorted_items %}
{# Try custom icon from frontmatter first, then fall back to type-based default #} {% set rendered_icon = icon(item.icon, size=20) if item.icon else '' %} {% if rendered_icon %} {{ rendered_icon }} {% elif item.type == 'section' %} {# Default folder icon for sections #} {{ icon('folder', size=20) }} {% else %} {# Default file icon for pages #} {{ icon('file-text', size=20) }} {% endif %}
{{ item.title }} {% if item.description %} {{ item.description }} {% endif %} {% if item.type == 'section' and item.page_count > 0 %} {{ item.page_count }} page{{ 's' if item.page_count != 1 }} {% elif item.type == 'page' and item.date %} {{ item.date | time_ago }} {% endif %}
{% endfor %}
{% endif %} {% endmacro %} {# Tag List Component Displays tags as styled badges, optionally linkable to tag archive pages. Args: tags: List of tag strings (required) small: Boolean (default: False) - use smaller size linkable: Boolean (default: True) - make tags clickable Example: {{ tag_list(page.tags) }} {{ tag_list(article.tags, small=True, linkable=False) }} #} {# Reusable iterator that skips the current page/item. Usage: {% from 'partials/content-components.html' import for_each_item_excluding_current with context %} {% call (post) for_each_item_excluding_current(posts) %} ... render using `post` ... {% endcall %} #} {% macro for_each_item_excluding_current(items) %} {%- for item in items -%} {%- if not (page and item._path == page._path) -%} {{ caller(item) }} {%- endif -%} {%- endfor -%} {% endmacro %} {# Render a section with a title only if there are any items. Args: - items: list to check (required) - title: section title string (required) - section_class: optional additional CSS classes on the section Usage: {% from 'partials/content-components.html' import render_section_if_any with context %} {% call (items) render_section_if_any(my_list, 'Functions', 'autodoc-section') %}
{% endcall %} #} {% macro render_section_if_any(items, title, section_class='') %} {% if items and (items | length) > 0 %}

{{ title }}

{{ caller(items) }}
{% endif %} {% endmacro %} {% macro tag_list(tags, small=False, linkable=True) %} {% set max_display = site.theme_config.config.get('max_tags_display', none) %} {% set display_tags = tags[:max_display] if max_display else tags %}
{% for tag in display_tags %} {% if linkable %} {# Use tag_url() function which handles baseurl and i18n prefixes correctly #} {{ tag }} {% else %} {{ tag }} {% endif %} {% endfor %} {% if max_display and tags|length > max_display %} +{{ tags|length - max_display }} more {% endif %}
{% endmacro %} {# Popular Tags Component Displays a tag cloud widget showing the most frequently used tags across the site. Args: limit: Number of tags to show (default: 10) Example: {{ popular_tags(limit=20) }} #} {% macro popular_tags_widget(limit=None) %} {% set tag_limit = limit if limit is not none else site.theme_config.config.get('popular_tags_count', 10) %} {% set top_tags = popular_tags(limit=tag_limit) %} {% if top_tags %} {% endif %} {% endmacro %} {# Random Posts Component Displays a widget with randomly selected posts from the site. Args: count: Number of random posts to show (default: 3) Example: {{ random_posts(count=5) }} #} {% macro random_posts(count=3) %} {% set random_posts = site.regular_pages | sample(count) %} {% if random_posts %} {% endif %} {% endmacro %}