Shared components for L1 and L2 servers: - Jinja2 template system with base template and components - Middleware for auth and content negotiation - Pydantic models for requests/responses - Utility functions for pagination, media, formatting - Constants for Tailwind/HTMX/Cytoscape CDNs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
83 lines
3.1 KiB
HTML
83 lines
3.1 KiB
HTML
{#
|
|
Pagination component with HTMX infinite scroll support.
|
|
|
|
Usage:
|
|
{% from "components/pagination.html" import infinite_scroll_trigger, page_links %}
|
|
|
|
{# Infinite scroll (HTMX) #}
|
|
{{ infinite_scroll_trigger(url="/items?page=2", colspan=3, has_more=True) }}
|
|
|
|
{# Traditional pagination #}
|
|
{{ page_links(current_page=1, total_pages=5, base_url="/items") }}
|
|
#}
|
|
|
|
{% macro infinite_scroll_trigger(url, colspan=1, has_more=True, target=None) %}
|
|
{% if has_more %}
|
|
<tr hx-get="{{ url }}"
|
|
hx-trigger="revealed"
|
|
hx-swap="afterend"
|
|
{% if target %}hx-target="{{ target }}"{% endif %}
|
|
class="htmx-indicator-row">
|
|
<td colspan="{{ colspan }}" class="text-center py-4">
|
|
<span class="text-gray-400 htmx-indicator">
|
|
<svg class="animate-spin h-5 w-5 inline mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Loading more...
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
{% endmacro %}
|
|
|
|
{% macro page_links(current_page, total_pages, base_url, class="") %}
|
|
<nav class="flex items-center justify-center space-x-2 {{ class }}">
|
|
{# Previous button #}
|
|
{% if current_page > 1 %}
|
|
<a href="{{ base_url }}?page={{ current_page - 1 }}"
|
|
class="px-3 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors">
|
|
← Previous
|
|
</a>
|
|
{% else %}
|
|
<span class="px-3 py-2 rounded-lg bg-dark-700 text-gray-500 cursor-not-allowed">
|
|
← Previous
|
|
</span>
|
|
{% endif %}
|
|
|
|
{# Page numbers #}
|
|
<div class="flex items-center space-x-1">
|
|
{% for page in range(1, total_pages + 1) %}
|
|
{% if page == current_page %}
|
|
<span class="px-3 py-2 rounded-lg bg-blue-600 text-white">{{ page }}</span>
|
|
{% elif page == 1 or page == total_pages or (page >= current_page - 2 and page <= current_page + 2) %}
|
|
<a href="{{ base_url }}?page={{ page }}"
|
|
class="px-3 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors">
|
|
{{ page }}
|
|
</a>
|
|
{% elif page == current_page - 3 or page == current_page + 3 %}
|
|
<span class="px-2 text-gray-500">...</span>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{# Next button #}
|
|
{% if current_page < total_pages %}
|
|
<a href="{{ base_url }}?page={{ current_page + 1 }}"
|
|
class="px-3 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors">
|
|
Next →
|
|
</a>
|
|
{% else %}
|
|
<span class="px-3 py-2 rounded-lg bg-dark-700 text-gray-500 cursor-not-allowed">
|
|
Next →
|
|
</span>
|
|
{% endif %}
|
|
</nav>
|
|
{% endmacro %}
|
|
|
|
{% macro page_info(page, limit, total) %}
|
|
<div class="text-sm text-gray-400">
|
|
Showing {{ (page - 1) * limit + 1 }}-{{ [page * limit, total] | min }} of {{ total }}
|
|
</div>
|
|
{% endmacro %}
|