Files
kennethreitz.org/templates/directory.html
T
kennethreitz f652f3f9ac Add mindmap tree view with search functionality
Replace D3.js force-directed graph with hierarchical tree interface.
Add HTMX-powered search with real-time filtering and highlighting.
Include statistics display for content overview.
2025-05-23 18:53:47 -04:00

335 lines
15 KiB
HTML

{% extends "base.html" %}
{% block content %}
<!-- Directory Header & Title -->
{% if index_content and content_position == 'top' %}
<!-- Short content displayed at the top -->
<div class="directory-content-top">
<article class="tufte">
<section class="tufte-content">
{{ index_content.content | safe }}
</section>
</article>
</div>
{% else %}
<div class="mb-12">
<h1 class="text-4xl font-bold text-gray-100 mb-4 tracking-tight font-serif page-title">
{{ title }}
</h1>
{% if current_path and current_path != '' %}
<p class="text-xl text-gray-400 font-serif italic et-book tracking-wide">{{ current_path }}</p>
{% endif %}
</div>
{% endif %}
{% if is_image_gallery and image_items %}
<!-- Image Gallery View -->
<div class="mb-8">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-gray-200 font-serif">Gallery</h2>
<div class="flex items-center gap-2">
<button id="grid-view" class="px-3 py-2 text-sm bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors">
Grid
</button>
<button id="list-view" class="px-3 py-2 text-sm bg-gray-700 text-gray-300 rounded-lg hover:bg-gray-600 transition-colors">
List
</button>
</div>
</div>
<!-- Image Grid -->
<div id="image-grid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 mb-8">
{% for item in image_items %}
<div class="group relative aspect-square overflow-hidden rounded-lg bg-gray-800 hover:scale-105 transition-transform duration-300">
<a href="{{ item.url_path }}" class="block w-full h-full">
<img src="{{ item.static_path }}"
alt="{{ item.display_name }}"
class="w-full h-full object-cover hover:scale-110 transition-transform duration-500"
loading="lazy">
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors duration-300"></div>
<div class="absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<p class="text-white text-sm font-medium truncate">{{ item.display_name }}</p>
</div>
</a>
</div>
{% endfor %}
</div>
<!-- Other Files (if any) -->
{% set non_image_items = items | rejectattr('is_image') | list %}
{% if non_image_items %}
<h3 class="text-xl font-semibold text-gray-300 mb-4 font-serif">Other Files</h3>
<div class="space-y-2">
{% for item in non_image_items %}
<div class="group flex items-center p-3 rounded-lg hover:bg-gray-800/70 transition-all duration-300 border border-transparent hover:border-gray-700 hover:border-primary-700/50 backdrop-blur-sm">
<!-- Item Icon -->
<div class="flex-shrink-0 w-10 text-center text-xl mr-4">
{% if item.is_dir %}
<span class="text-primary-400">📁</span>
{% elif item.is_markdown %}
<span class="text-blue-400">📝</span>
{% else %}
<span class="text-gray-400">📄</span>
{% endif %}
</div>
<!-- Item Content -->
<div class="flex-grow flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<a href="{{ item.url_path }}"
class="text-lg font-medium {% if item.is_dir %}text-primary-300{% else %}text-gray-200 et-book{% endif %} hover:text-primary-400 transition-colors duration-200 group-hover:text-primary-300 tracking-wide">
{{ item.display_name }}{% if item.is_dir %}/{% endif %}
</a>
<!-- Item Meta -->
<div class="flex items-center gap-4 text-sm text-gray-400">
<span class="font-mono">
{{ item.modified.strftime('%b %d, %Y') }}
</span>
{% if item.is_dir %}
<span class="px-2 py-1 bg-primary-900/60 text-primary-300 rounded-full text-xs font-medium uppercase tracking-wide border border-primary-700/30">
folder
</span>
{% elif not item.is_markdown %}
<span class="px-2 py-1 bg-gray-800/60 text-gray-300 rounded-full text-xs font-medium uppercase tracking-wide border border-gray-700/30">
{{ item.file_type.replace('.', '') or 'file' }}
</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Hidden List View (for toggle) -->
<div id="list-view-container" class="hidden mb-12 mt-8">
<div class="space-y-2">
{% for item in items %}
<div class="group flex items-center p-4 rounded-lg hover:bg-gray-800/70 transition-all duration-300 border border-transparent hover:border-gray-700 hover:border-primary-700/50 backdrop-blur-sm">
<!-- Item Icon -->
<div class="flex-shrink-0 w-10 text-center text-xl mr-4">
{% if item.is_dir %}
<span class="text-primary-400">📁</span>
{% elif item.is_markdown %}
<span class="text-blue-400">📝</span>
{% elif item.is_image %}
<span class="text-green-400">🖼️</span>
{% else %}
<span class="text-gray-400">📄</span>
{% endif %}
</div>
<!-- Item Content -->
<div class="flex-grow flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<a href="{{ item.url_path }}"
class="text-lg font-medium {% if item.is_dir %}text-primary-300{% else %}text-gray-200 et-book{% endif %} hover:text-primary-400 transition-colors duration-200 group-hover:text-primary-300 tracking-wide">
{{ item.display_name }}{% if item.is_dir %}/{% endif %}
</a>
<!-- Item Meta -->
<div class="flex items-center gap-4 text-sm text-gray-400">
<span class="font-mono">
{{ item.modified.strftime('%b %d, %Y') }}
</span>
{% if item.is_dir %}
<span class="px-2 py-1 bg-primary-900/60 text-primary-300 rounded-full text-xs font-medium uppercase tracking-wide border border-primary-700/30">
folder
</span>
{% elif item.is_image %}
<span class="px-2 py-1 bg-green-900/60 text-green-300 rounded-full text-xs font-medium uppercase tracking-wide border border-green-700/30">
image
</span>
{% elif not item.is_markdown %}
<span class="px-2 py-1 bg-gray-800/60 text-gray-300 rounded-full text-xs font-medium uppercase tracking-wide border border-gray-700/30">
{{ item.file_type.replace('.', '') or 'file' }}
</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% elif items %}
<!-- Regular Directory Listing -->
<div class="space-y-2 mb-12 mt-8">
{% for item in items %}
<div class="group flex items-center p-4 rounded-lg hover:bg-gray-800/70 transition-all duration-300 border border-transparent hover:border-gray-700 hover:border-primary-700/50 backdrop-blur-sm">
<!-- Item Icon -->
<div class="flex-shrink-0 w-10 text-center text-xl mr-4">
{% if item.is_dir %}
<span class="text-primary-400">📁</span>
{% elif item.is_markdown %}
<span class="text-blue-400">📝</span>
{% elif item.is_image %}
<span class="text-green-400">🖼️</span>
{% else %}
<span class="text-gray-400">📄</span>
{% endif %}
</div>
<!-- Item Content -->
<div class="flex-grow flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<a href="{{ item.url_path }}"
class="text-lg font-medium {% if item.is_dir %}text-primary-300{% else %}text-gray-200 et-book{% endif %} hover:text-primary-400 transition-colors duration-200 group-hover:text-primary-300 tracking-wide">
{{ item.display_name }}{% if item.is_dir %}/{% endif %}
</a>
<!-- Item Meta -->
<div class="flex items-center gap-4 text-sm text-gray-400">
<span class="font-mono">
{{ item.modified.strftime('%b %d, %Y') }}
</span>
{% if item.is_dir %}
<span class="px-2 py-1 bg-primary-900/60 text-primary-300 rounded-full text-xs font-medium uppercase tracking-wide border border-primary-700/30">
folder
</span>
{% elif item.is_image %}
<span class="px-2 py-1 bg-green-900/60 text-green-300 rounded-full text-xs font-medium uppercase tracking-wide border border-green-700/30">
image
</span>
{% elif not item.is_markdown %}
<span class="px-2 py-1 bg-gray-800/60 text-gray-300 rounded-full text-xs font-medium uppercase tracking-wide border border-gray-700/30">
{{ item.file_type.replace('.', '') or 'file' }}
</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<!-- Empty Directory -->
<div class="text-center py-16 bg-gray-800/20 rounded-lg backdrop-blur-sm">
<div class="text-6xl mb-6 opacity-40">📂</div>
<p class="text-xl text-gray-400 font-serif italic et-book tracking-wide">
This directory doesn't contain any files or folders yet.
</p>
</div>
{% endif %}
<!-- Long content displayed at the bottom -->
{% if index_content and content_position == 'bottom' %}
<div class="directory-content-bottom bg-gray-800/30 backdrop-blur-sm border border-gray-700/30">
<h2 class="directory-content-title">About This Directory</h2>
<article class="tufte">
<section class="tufte-content">
{{ index_content.content | safe }}
</section>
</article>
</div>
{% endif %}
{% endblock %}
{% block extra_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// View toggle functionality for image galleries
const gridViewBtn = document.getElementById('grid-view');
const listViewBtn = document.getElementById('list-view');
const imageGrid = document.getElementById('image-grid');
const listViewContainer = document.getElementById('list-view-container');
if (gridViewBtn && listViewBtn && imageGrid && listViewContainer) {
gridViewBtn.addEventListener('click', function() {
imageGrid.parentElement.classList.remove('hidden');
listViewContainer.classList.add('hidden');
gridViewBtn.classList.add('bg-primary-600', 'text-white');
gridViewBtn.classList.remove('bg-gray-700', 'text-gray-300');
listViewBtn.classList.add('bg-gray-700', 'text-gray-300');
listViewBtn.classList.remove('bg-primary-600', 'text-white');
});
listViewBtn.addEventListener('click', function() {
imageGrid.parentElement.classList.add('hidden');
listViewContainer.classList.remove('hidden');
listViewBtn.classList.add('bg-primary-600', 'text-white');
listViewBtn.classList.remove('bg-gray-700', 'text-gray-300');
gridViewBtn.classList.add('bg-gray-700', 'text-gray-300');
gridViewBtn.classList.remove('bg-primary-600', 'text-white');
});
}
// Enhanced keyboard navigation
const items = document.querySelectorAll('.group a');
let currentIndex = -1;
document.addEventListener('keydown', function(e) {
if (e.key === 'ArrowDown' || e.key === 'j') {
e.preventDefault();
currentIndex = Math.min(currentIndex + 1, items.length - 1);
focusItem(currentIndex);
} else if (e.key === 'ArrowUp' || e.key === 'k') {
e.preventDefault();
currentIndex = Math.max(currentIndex - 1, 0);
focusItem(currentIndex);
} else if (e.key === 'Enter' && currentIndex >= 0) {
e.preventDefault();
items[currentIndex].click();
}
});
function focusItem(index) {
items.forEach((item, i) => {
const parent = item.closest('.group');
if (i === index) {
item.focus();
parent.classList.add('bg-gray-800/70', 'border-primary-700/50');
} else {
parent.classList.remove('bg-gray-800/70', 'border-primary-700/50');
}
});
}
// Add elegant animation on page load
const directoryItems = document.querySelectorAll('.group');
directoryItems.forEach((item, index) => {
item.style.opacity = '0';
item.style.transform = 'translateY(20px) scale(0.98)';
setTimeout(() => {
item.style.transition = 'all 0.6s cubic-bezier(0.16, 1, 0.3, 1)';
item.style.opacity = '1';
item.style.transform = 'translateY(0) scale(1)';
}, index * 60);
});
// Add animation for image grid items
const gridItems = document.querySelectorAll('#image-grid > div');
gridItems.forEach((item, index) => {
item.style.opacity = '0';
item.style.transform = 'translateY(20px) scale(0.95)';
setTimeout(() => {
item.style.transition = 'all 0.5s cubic-bezier(0.16, 1, 0.3, 1)';
item.style.opacity = '1';
item.style.transform = 'translateY(0) scale(1)';
}, index * 80);
});
// Add file type icons based on extension
const fileLinks = document.querySelectorAll('.group a');
fileLinks.forEach(link => {
const href = link.getAttribute('href');
if (href && !link.querySelector('.file-icon')) {
let icon = '';
if (href.includes('.pdf')) icon = '📕';
else if (href.includes('.doc') || href.includes('.docx')) icon = '📘';
else if (href.includes('.xls') || href.includes('.xlsx')) icon = '📊';
else if (href.includes('.zip') || href.includes('.tar')) icon = '📦';
else if (href.includes('.mp3') || href.includes('.wav')) icon = '🎵';
else if (href.includes('.mp4') || href.includes('.mov')) icon = '🎬';
if (icon) {
const iconSpan = document.createElement('span');
iconSpan.textContent = ` ${icon}`;
iconSpan.className = 'file-icon text-sm opacity-60';
link.appendChild(iconSpan);
}
}
});
});
</script>
{% endblock %}