Fix AJAX navigation, WebSocket script scope, and Daphne media serving 404s
This commit is contained in:
@@ -324,8 +324,12 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-root">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- Build version badge -->
|
||||
<div id="app-version" style="position:fixed;bottom:4px;left:4px;font-size:0.6rem;color:rgba(255,255,255,0.25);z-index:9999;pointer-events:none;font-family:monospace;">v2026.05.24-b</div>
|
||||
<!-- Voice connected status panel -->
|
||||
<div class="voice-status-panel" id="voice-status-panel">
|
||||
<div class="voice-status-info">
|
||||
@@ -341,7 +345,7 @@
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
<script id="base-script">
|
||||
(function() {
|
||||
const currentUserId = {% if request.user.is_authenticated %}{{ request.user.id }}{% else %}null{% endif %};
|
||||
const currentUsername = "{% if request.user.is_authenticated %}{{ request.user.username|escapejs }}{% endif %}";
|
||||
@@ -596,15 +600,20 @@
|
||||
});
|
||||
|
||||
// AJAX Shell Navigation
|
||||
// Pages that should always do a full-page load (non-SPA layouts, auth pages)
|
||||
const FULL_PAGE_KEYWORDS = ['create', 'profile', 'admin', 'login', 'register', 'logout', 'delete', 'block'];
|
||||
|
||||
function shouldFullPageNavigate(href) {
|
||||
if (!href || href.startsWith('http') || href.startsWith('#') || href.startsWith('javascript')) return true;
|
||||
return FULL_PAGE_KEYWORDS.some(kw => href.includes(kw));
|
||||
}
|
||||
|
||||
document.body.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a');
|
||||
if (!link) return;
|
||||
|
||||
const href = link.getAttribute('href');
|
||||
if (!href || href.startsWith('http') || href.startsWith('#') || href.includes('delete') || href.includes('logout') || href.includes('admin') || href.includes('profile')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldFullPageNavigate(href)) return;
|
||||
if (link.target === '_blank') return;
|
||||
|
||||
e.preventDefault();
|
||||
@@ -612,35 +621,47 @@
|
||||
});
|
||||
|
||||
function loadPage(url) {
|
||||
// Close existing channel/DM WebSocket before navigating
|
||||
if (window.channelWs) {
|
||||
window.channelWs.close();
|
||||
window.channelWs = null;
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then(res => res.text())
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return res.text();
|
||||
})
|
||||
.then(html => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
const newMain = doc.querySelector('.main-content');
|
||||
const currentMain = document.querySelector('.main-content');
|
||||
if (newMain && currentMain) {
|
||||
currentMain.replaceWith(newMain);
|
||||
}
|
||||
const newRoot = doc.getElementById('app-root');
|
||||
const currentRoot = document.getElementById('app-root');
|
||||
|
||||
document.querySelectorAll('.channel-item.active').forEach(el => el.classList.remove('active'));
|
||||
const activeLink = document.querySelector(`.channel-item[href="${url}"]`);
|
||||
if (activeLink) activeLink.classList.add('active');
|
||||
|
||||
if (window.channelWs) {
|
||||
window.channelWs.close();
|
||||
if (newRoot && currentRoot) {
|
||||
currentRoot.replaceWith(newRoot);
|
||||
} else {
|
||||
// Fallback: full page load if structure doesn't match
|
||||
window.location.href = url;
|
||||
return;
|
||||
}
|
||||
|
||||
history.pushState({ url }, '', url);
|
||||
|
||||
// Execute only PAGE-SPECIFIC scripts (those inside the scripts block).
|
||||
// SKIP the base-script IIFE — it's already running and re-executing
|
||||
// it would duplicate global WebSocket connections and event handlers.
|
||||
// Wrap each script in an IIFE to prevent 'const' redeclaration errors
|
||||
// when navigating between channels (each has const channelId, etc.).
|
||||
doc.querySelectorAll('script').forEach(script => {
|
||||
if (!script.src && (script.textContent.includes('WebSocket') || script.textContent.includes('channelWs'))) {
|
||||
const newScript = document.createElement('script');
|
||||
newScript.textContent = script.textContent;
|
||||
document.body.appendChild(newScript);
|
||||
newScript.remove();
|
||||
}
|
||||
if (script.id === 'base-script') return;
|
||||
if (script.src) return;
|
||||
if (!script.textContent.trim()) return;
|
||||
const newScript = document.createElement('script');
|
||||
newScript.textContent = `(function(){${script.textContent}})();`;
|
||||
document.body.appendChild(newScript);
|
||||
newScript.remove();
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -657,27 +678,36 @@
|
||||
}
|
||||
};
|
||||
|
||||
// AJAX form submit handler — only for file upload forms inside chat/DM views.
|
||||
// Forms for auth, profile, channel create, admin actions submit natively.
|
||||
document.body.addEventListener('submit', function(e) {
|
||||
const form = e.target.closest('form');
|
||||
if (!form || form.getAttribute('action')?.includes('login') || form.getAttribute('action')?.includes('register') || form.getAttribute('action')?.includes('profile') || form.getAttribute('action')?.includes('create')) {
|
||||
return;
|
||||
}
|
||||
if (!form) return;
|
||||
|
||||
const action = form.getAttribute('action') || '';
|
||||
// Let these forms submit natively (full page reload)
|
||||
if (FULL_PAGE_KEYWORDS.some(kw => action.includes(kw))) return;
|
||||
|
||||
// Only AJAX-submit file upload forms (they have enctype=multipart)
|
||||
if (!form.enctype || form.enctype !== 'multipart/form-data') return;
|
||||
|
||||
e.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
const action = form.getAttribute('action') || location.pathname;
|
||||
const submitUrl = action || location.pathname;
|
||||
|
||||
fetch(action, {
|
||||
fetch(submitUrl, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
// Reload current page to show the uploaded file
|
||||
loadPage(location.pathname);
|
||||
})
|
||||
.catch(err => console.error("Upload form failed:", err));
|
||||
.catch(err => console.error("File upload failed:", err));
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user