mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 09:31:09 -05:00
Revert "Add version control system functionality with branch management, history tracking, and merge operations"
This reverts commit f3d28817a5.
This commit is contained in:
@@ -146,196 +146,152 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container max-w-6xl px-4 py-6 mx-auto">
|
||||
<div id="dashboard-content"
|
||||
class="relative transition-all duration-200"
|
||||
hx-target="this"
|
||||
hx-push-url="true"
|
||||
hx-indicator="#loading-skeleton"
|
||||
hx-swap="outerHTML">
|
||||
<div id="dashboard-content" class="relative transition-all duration-200">
|
||||
{% block moderation_content %}
|
||||
{% include "moderation/partials/dashboard_content.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<!-- Loading Skeleton -->
|
||||
<div class="absolute inset-0 htmx-indicator opacity-0"
|
||||
id="loading-skeleton"
|
||||
aria-hidden="true">
|
||||
<div class="absolute inset-0 htmx-indicator" id="loading-skeleton">
|
||||
{% include "moderation/partials/loading_skeleton.html" %}
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div class="absolute inset-0 hidden"
|
||||
id="error-state"
|
||||
role="alert"
|
||||
aria-live="assertive">
|
||||
<div class="flex flex-col items-center justify-center h-full p-6 space-y-4 text-center"
|
||||
x-data="{ errorMessage: 'There was a problem loading the content. Please try again.' }"
|
||||
x-init="$watch('errorMessage', value => $dispatch('show-toast', { message: value, type: 'error' }))">
|
||||
<div class="absolute inset-0 hidden" id="error-state">
|
||||
<div class="flex flex-col items-center justify-center h-full p-6 space-y-4 text-center">
|
||||
<div class="p-4 text-red-500 bg-red-100 rounded-full dark:bg-red-900/40">
|
||||
<i class="text-4xl fas fa-exclamation-circle" aria-hidden="true"></i>
|
||||
<i class="text-4xl fas fa-exclamation-circle"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-red-600 dark:text-red-400">
|
||||
Something went wrong
|
||||
</h3>
|
||||
<p class="max-w-md text-gray-600 dark:text-gray-400"
|
||||
id="error-message"
|
||||
x-text="errorMessage"></p>
|
||||
<button class="px-4 py-2 font-medium text-white transition-all duration-200 bg-red-600 rounded-lg hover:bg-red-500 dark:bg-red-700 dark:hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
hx-get="{{ request.path }}"
|
||||
hx-target="#dashboard-content"
|
||||
hx-push-url="true"
|
||||
hx-indicator="this"
|
||||
@click="$el.disabled = true"
|
||||
hx-on::after-request="$el.disabled = false">
|
||||
<span class="htmx-indicator">
|
||||
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>
|
||||
Retrying...
|
||||
</span>
|
||||
<span class="htmx-settled">
|
||||
<i class="mr-2 fas fa-sync-alt" aria-hidden="true"></i>
|
||||
Retry
|
||||
</span>
|
||||
<p class="max-w-md text-gray-600 dark:text-gray-400" id="error-message">
|
||||
There was a problem loading the content. Please try again.
|
||||
</p>
|
||||
<button class="px-4 py-2 font-medium text-white transition-all duration-200 bg-red-600 rounded-lg hover:bg-red-500 dark:bg-red-700 dark:hover:bg-red-600"
|
||||
onclick="window.location.reload()">
|
||||
<i class="mr-2 fas fa-sync-alt"></i>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Notifications -->
|
||||
<div id="toast-container"
|
||||
class="fixed bottom-4 right-4 z-50 space-y-2"
|
||||
x-data="{
|
||||
toasts: [],
|
||||
add(message, type = 'success') {
|
||||
const id = Date.now();
|
||||
this.toasts.push({ id, message, type });
|
||||
setTimeout(() => this.remove(id), 5000);
|
||||
},
|
||||
remove(id) {
|
||||
this.toasts = this.toasts.filter(t => t.id !== id);
|
||||
}
|
||||
}"
|
||||
@show-toast.window="add($event.detail.message, $event.detail.type)"
|
||||
@htmx:responseError.window="add($event.detail.error || 'An error occurred', 'error')"
|
||||
aria-live="polite"
|
||||
aria-atomic="true">
|
||||
<template x-for="toast in toasts" :key="toast.id">
|
||||
<div class="flex items-center p-4 rounded-lg shadow-lg transform transition-all duration-300"
|
||||
:class="{
|
||||
'bg-green-600': toast.type === 'success',
|
||||
'bg-red-600': toast.type === 'error',
|
||||
'bg-yellow-600': toast.type === 'warning',
|
||||
'bg-blue-600': toast.type === 'info'
|
||||
}"
|
||||
x-transition:enter="ease-out"
|
||||
x-transition:enter-start="opacity-0 translate-y-2"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="ease-in"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0">
|
||||
<div class="flex-1 text-white">
|
||||
<p class="font-medium" x-text="toast.message"></p>
|
||||
</div>
|
||||
<button @click="remove(toast.id)"
|
||||
class="ml-4 text-white hover:text-white/80"
|
||||
aria-label="Close notification">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- HTMX Event Handlers -->
|
||||
<script>
|
||||
document.body.addEventListener('htmx:beforeRequest', function(evt) {
|
||||
const target = evt.detail.target;
|
||||
if (target.hasAttribute('hx-disabled-elt')) {
|
||||
const disabledElt = document.querySelector(target.getAttribute('hx-disabled-elt'));
|
||||
if (disabledElt) {
|
||||
disabledElt.disabled = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||
const target = evt.detail.target;
|
||||
if (target.hasAttribute('hx-disabled-elt')) {
|
||||
const disabledElt = document.querySelector(target.getAttribute('hx-disabled-elt'));
|
||||
if (disabledElt) {
|
||||
disabledElt.disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:responseError', function(evt) {
|
||||
const errorToast = new CustomEvent('show-toast', {
|
||||
detail: {
|
||||
message: evt.detail.error || 'An error occurred while processing your request',
|
||||
type: 'error'
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(errorToast);
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:sendError', function(evt) {
|
||||
const errorToast = new CustomEvent('show-toast', {
|
||||
detail: {
|
||||
message: 'Network error: Could not connect to the server',
|
||||
type: 'error'
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(errorToast);
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<!-- Base HTMX Configuration -->
|
||||
<script>
|
||||
// HTMX Configuration and Enhancements
|
||||
document.body.addEventListener('htmx:configRequest', function(evt) {
|
||||
evt.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
|
||||
});
|
||||
|
||||
// Loading and Error State Management
|
||||
const dashboard = {
|
||||
content: document.getElementById('dashboard-content'),
|
||||
skeleton: document.getElementById('loading-skeleton'),
|
||||
errorState: document.getElementById('error-state'),
|
||||
errorMessage: document.getElementById('error-message'),
|
||||
|
||||
showLoading() {
|
||||
this.content.setAttribute('aria-busy', 'true');
|
||||
this.content.style.opacity = '0';
|
||||
this.errorState.classList.add('hidden');
|
||||
},
|
||||
|
||||
hideLoading() {
|
||||
this.content.setAttribute('aria-busy', 'false');
|
||||
this.content.style.opacity = '1';
|
||||
},
|
||||
|
||||
showError(message) {
|
||||
this.errorState.classList.remove('hidden');
|
||||
this.errorMessage.textContent = message || 'There was a problem loading the content. Please try again.';
|
||||
// Announce error to screen readers
|
||||
this.errorMessage.setAttribute('role', 'alert');
|
||||
}
|
||||
};
|
||||
|
||||
// Enhanced HTMX Event Handlers
|
||||
document.body.addEventListener('htmx:beforeRequest', function(evt) {
|
||||
if (evt.detail.target.id === 'dashboard-content') {
|
||||
dashboard.showLoading();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:afterOnLoad', function(evt) {
|
||||
if (evt.detail.target.id === 'dashboard-content') {
|
||||
dashboard.hideLoading();
|
||||
// Reset focus for accessibility
|
||||
const firstFocusable = evt.detail.target.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||
if (firstFocusable) {
|
||||
firstFocusable.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:responseError', function(evt) {
|
||||
if (evt.detail.target.id === 'dashboard-content') {
|
||||
dashboard.showError(evt.detail.error);
|
||||
}
|
||||
});
|
||||
|
||||
// Search Input Debouncing
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Apply debouncing to search inputs
|
||||
document.querySelectorAll('[data-search]').forEach(input => {
|
||||
const originalSearch = () => {
|
||||
htmx.trigger(input, 'input');
|
||||
};
|
||||
const debouncedSearch = debounce(originalSearch, 300);
|
||||
|
||||
input.addEventListener('input', (e) => {
|
||||
e.preventDefault();
|
||||
debouncedSearch();
|
||||
});
|
||||
});
|
||||
|
||||
// Virtual Scrolling for Large Lists
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: '100px',
|
||||
threshold: 0.1
|
||||
};
|
||||
|
||||
const loadMoreContent = (entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !entry.target.classList.contains('loading')) {
|
||||
entry.target.classList.add('loading');
|
||||
htmx.trigger(entry.target, 'intersect');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(loadMoreContent, observerOptions);
|
||||
document.querySelectorAll('[data-infinite-scroll]').forEach(el => observer.observe(el));
|
||||
|
||||
// Keyboard Navigation Enhancement
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
const openModals = document.querySelectorAll('[x-show="showNotes"]');
|
||||
openModals.forEach(modal => {
|
||||
const alpineData = modal.__x.$data;
|
||||
if (alpineData.showNotes) {
|
||||
alpineData.showNotes = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Custom Moderation JS -->
|
||||
<script src="{% static 'js/moderation.js' %}"></script>
|
||||
|
||||
<!-- Enhanced Mobile Styles -->
|
||||
<style>
|
||||
@media (max-width: 640px) {
|
||||
.action-buttons {
|
||||
@apply flex-col w-full space-y-2;
|
||||
}
|
||||
|
||||
.action-buttons > button {
|
||||
@apply w-full justify-center;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
@apply fixed bottom-0 left-0 right-0 max-h-[50vh] overflow-y-auto bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 rounded-t-xl shadow-xl;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
@apply grid-cols-1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Touch Device Optimizations */
|
||||
@media (hover: none) {
|
||||
.touch-target {
|
||||
@apply min-h-[44px] min-w-[44px] p-2;
|
||||
}
|
||||
|
||||
.touch-friendly-select {
|
||||
@apply py-2.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Accessibility Improvements -->
|
||||
<div id="a11y-announcer"
|
||||
class="sr-only"
|
||||
aria-live="polite"
|
||||
aria-atomic="true">
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user