Fix: resolve JS SyntaxErrors that crashed entire window.lt namespace
Root cause: two SyntaxErrors prevented the IIFE from completing,
meaning window.lt was never created and all features were broken.
Fixes applied:
1. CRITICAL: Renamed auth's apiFetch redeclaration → _apiFetchAuth
(duplicate function declaration in strict-mode IIFE = SyntaxError)
2. CRITICAL: Renamed toast queue vars (_toastQueue/_toastActive already
declared by original showToast in outer scope = SyntaxError)
Replaced heavy queue wrapper with lightweight progress-bar injector
3. timer.countdown: classList.add(urgentClass) throws on space-separated
class string — split/filter/forEach now used
4. contextMenu: _ctxItems declared as [] but used as string-keyed object
— changed to {}
5. Split pane divider: background was border-dim (7% opacity, invisible)
— raised to border-color (16%), added ::before hit-area expansion,
hover state uses accent-cyan for clear visual feedback
6. Light theme: html background-image was hardcoded cyan rgba, didn't
update with data-theme — added explicit html[data-theme="light"] rule
7. Light theme: .lt-header background was hardcoded rgba(3,5,8,0.96)
— added explicit light-mode override
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3325,15 +3325,17 @@ html[data-theme="light"] {
|
|||||||
/* Hide CRT overlays in light mode */
|
/* Hide CRT overlays in light mode */
|
||||||
html[data-theme="light"] body::before,
|
html[data-theme="light"] body::before,
|
||||||
html[data-theme="light"] body::after { display: none; }
|
html[data-theme="light"] body::after { display: none; }
|
||||||
/* Soften dot grid */
|
/* Override hardcoded dot-grid background on html element */
|
||||||
html[data-theme="light"] body {
|
html[data-theme="light"] {
|
||||||
background-image: radial-gradient(circle, rgba(0,80,160,0.10) 1px, transparent 1px);
|
background-image: radial-gradient(circle, rgba(0,80,160,0.12) 1px, transparent 1px);
|
||||||
}
|
}
|
||||||
/* Nav / header adjustments */
|
/* Header — hardcoded dark rgba needs explicit light override */
|
||||||
html[data-theme="light"] .lt-header {
|
html[data-theme="light"] .lt-header {
|
||||||
background: rgba(238,241,246,0.96);
|
background: rgba(238,241,246,0.97);
|
||||||
border-bottom-color: var(--border-color);
|
border-bottom-color: var(--border-color);
|
||||||
}
|
}
|
||||||
|
html[data-theme="light"] .lt-main { background: transparent; }
|
||||||
|
html[data-theme="light"] .lt-section-header { background: linear-gradient(90deg, var(--bg-secondary) 0%, transparent 100%); }
|
||||||
html[data-theme="light"] .lt-nav-link,
|
html[data-theme="light"] .lt-nav-link,
|
||||||
html[data-theme="light"] .lt-nav-link:hover { color: var(--text-secondary); }
|
html[data-theme="light"] .lt-nav-link:hover { color: var(--text-secondary); }
|
||||||
html[data-theme="light"] .lt-nav-link.active { color: var(--accent-orange); }
|
html[data-theme="light"] .lt-nav-link.active { color: var(--accent-orange); }
|
||||||
@@ -3943,12 +3945,20 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
|||||||
}
|
}
|
||||||
.lt-split-divider {
|
.lt-split-divider {
|
||||||
flex: 0 0 5px;
|
flex: 0 0 5px;
|
||||||
background: var(--border-dim);
|
background: var(--border-color); /* 16% opacity — clearly visible */
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
transition: background 0.15s;
|
transition: background 0.15s;
|
||||||
position: relative;
|
position: relative;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
|
/* Wider invisible hit area so it's easy to grab */
|
||||||
|
}
|
||||||
|
.lt-split-divider::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
left: -6px; right: -6px;
|
||||||
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
.lt-split-divider::after {
|
.lt-split-divider::after {
|
||||||
content: '⠿';
|
content: '⠿';
|
||||||
@@ -3956,11 +3966,12 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
|||||||
top: 50%; left: 50%;
|
top: 50%; left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 0.6rem;
|
font-size: 0.75rem;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.lt-split-divider:hover,
|
.lt-split-divider:hover,
|
||||||
.lt-split-divider.is-dragging { background: var(--accent-cyan-border); }
|
.lt-split-divider.is-dragging { background: var(--accent-cyan); }
|
||||||
|
.lt-split--vertical .lt-split-divider::before { top: -6px; bottom: -6px; left: 0; right: 0; cursor: row-resize; }
|
||||||
.lt-split--vertical .lt-split-divider { cursor: row-resize; }
|
.lt-split--vertical .lt-split-divider { cursor: row-resize; }
|
||||||
/* On mobile, stack vertically and hide divider */
|
/* On mobile, stack vertically and hide divider */
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
|
|||||||
@@ -1582,7 +1582,7 @@
|
|||||||
items = [{ label, icon, kbd, danger, divider, action }]
|
items = [{ label, icon, kbd, danger, divider, action }]
|
||||||
================================================================ */
|
================================================================ */
|
||||||
let _ctxMenu = null;
|
let _ctxMenu = null;
|
||||||
let _ctxItems = [];
|
const _ctxItems = {};
|
||||||
function _ctxShow(x, y, items) {
|
function _ctxShow(x, y, items) {
|
||||||
if (!_ctxMenu) {
|
if (!_ctxMenu) {
|
||||||
_ctxMenu = document.createElement('div');
|
_ctxMenu = document.createElement('div');
|
||||||
@@ -2028,49 +2028,31 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* ================================================================
|
/* ================================================================
|
||||||
MODULE 47 — TOAST QUEUE (enhanced dispatch)
|
MODULE 47 — TOAST ENHANCEMENTS
|
||||||
Wraps existing toast module with max-stack + progress bars
|
Adds a drain progress bar to each toast.
|
||||||
|
The original showToast already handles queuing (section 2).
|
||||||
================================================================ */
|
================================================================ */
|
||||||
const _toastMaxStack = 5;
|
// Patch showToast to inject a progress bar after each toast is created
|
||||||
const _toastQueue = [];
|
const _origShowToast = showToast;
|
||||||
let _toastActive = 0;
|
function showToast(message, type, duration) {
|
||||||
const _origToast = Object.assign({}, toast);
|
duration = duration || 4000;
|
||||||
|
const result = _origShowToast(message, type, duration);
|
||||||
function _toastEnqueue(type, msg, dur = 4000) {
|
// Inject drain bar into the most recently added toast
|
||||||
if (_toastActive >= _toastMaxStack) {
|
requestAnimationFrame(() => {
|
||||||
_toastQueue.push({ type, msg, dur });
|
const container = document.getElementById('lt-toast-container');
|
||||||
return;
|
if (!container) return;
|
||||||
}
|
const toasts = container.querySelectorAll('.lt-toast');
|
||||||
_toastActive++;
|
const last = toasts[toasts.length - 1];
|
||||||
const id = _origToast[type] ? _origToast[type](msg, dur) : _origToast.info(msg, dur);
|
if (last && !last.querySelector('.lt-toast-progress')) {
|
||||||
// Add progress bar to the toast element
|
|
||||||
setTimeout(() => {
|
|
||||||
const toastEl = document.querySelector(`#lt-toast-container .lt-toast:last-child`);
|
|
||||||
if (toastEl && !toastEl.querySelector('.lt-toast-progress')) {
|
|
||||||
const bar = document.createElement('div');
|
const bar = document.createElement('div');
|
||||||
bar.className = 'lt-toast-progress';
|
bar.className = 'lt-toast-progress';
|
||||||
bar.style.animationDuration = dur + 'ms';
|
bar.style.animationDuration = duration + 'ms';
|
||||||
toastEl.appendChild(bar);
|
last.appendChild(bar);
|
||||||
}
|
}
|
||||||
}, 20);
|
});
|
||||||
setTimeout(() => {
|
return result;
|
||||||
_toastActive = Math.max(0, _toastActive - 1);
|
|
||||||
if (_toastQueue.length) {
|
|
||||||
const next = _toastQueue.shift();
|
|
||||||
_toastEnqueue(next.type, next.msg, next.dur);
|
|
||||||
}
|
|
||||||
}, dur + 400);
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override toast methods to use queue
|
|
||||||
['success','error','warning','info'].forEach(t => {
|
|
||||||
if (toast[t]) {
|
|
||||||
const orig = toast[t].bind(toast);
|
|
||||||
toast[t] = (msg, dur) => _toastEnqueue(t, msg, dur || 4000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ================================================================
|
/* ================================================================
|
||||||
MODULE 48 — SIDEBAR SUBMENUS
|
MODULE 48 — SIDEBAR SUBMENUS
|
||||||
Auto-inits .lt-sidebar-group elements.
|
Auto-inits .lt-sidebar-group elements.
|
||||||
@@ -2358,7 +2340,7 @@
|
|||||||
clearInterval(handle);
|
clearInterval(handle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (diff <= urgent) dom.classList.add(urgentClass);
|
if (diff <= urgent) urgentClass.split(/\s+/).filter(Boolean).forEach(c => dom.classList.add(c));
|
||||||
const h = Math.floor(diff / 3600), m = Math.floor((diff % 3600) / 60), s = diff % 60;
|
const h = Math.floor(diff / 3600), m = Math.floor((diff % 3600) / 60), s = diff % 60;
|
||||||
dom.textContent = [h, m, s].map(n => String(n).padStart(2, '0')).join(':');
|
dom.textContent = [h, m, s].map(n => String(n).padStart(2, '0')).join(':');
|
||||||
}
|
}
|
||||||
@@ -2551,18 +2533,15 @@
|
|||||||
isExpiringSoon: (secs = 60) => _authExpiry > 0 && Date.now() >= _authExpiry - secs * 1000,
|
isExpiringSoon: (secs = 60) => _authExpiry > 0 && Date.now() >= _authExpiry - secs * 1000,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Patch lt.api to inject Authorization header and auto-refresh
|
// Patch lt.api — auth-aware wrapper (renamed to avoid strict-mode duplicate declaration)
|
||||||
const _origApiFetch = apiFetch;
|
async function _apiFetchAuth(method, url, body) {
|
||||||
async function apiFetch(method, url, body) {
|
if (_authAccess && auth.isExpiringSoon()) await auth.refresh();
|
||||||
if (_authAccess) {
|
|
||||||
if (auth.isExpiringSoon()) await auth.refresh();
|
|
||||||
}
|
|
||||||
const opts = { method, headers: Object.assign({ 'Content-Type': 'application/json' }, csrfHeaders()) };
|
const opts = { method, headers: Object.assign({ 'Content-Type': 'application/json' }, csrfHeaders()) };
|
||||||
if (_authAccess) opts.headers['Authorization'] = 'Bearer ' + _authAccess;
|
if (_authAccess) opts.headers['Authorization'] = 'Bearer ' + _authAccess;
|
||||||
if (body !== undefined) opts.body = JSON.stringify(body);
|
if (body !== undefined) opts.body = JSON.stringify(body);
|
||||||
let resp;
|
let resp;
|
||||||
try { resp = await fetch(url, opts); } catch (err) { throw new Error('Network error: ' + err.message); }
|
try { resp = await fetch(url, opts); } catch (err) { throw new Error('Network error: ' + err.message); }
|
||||||
// Auto-retry once on 401 after token refresh
|
// Auto-retry once on 401 after silent token refresh
|
||||||
if (resp.status === 401 && _authRefresh) {
|
if (resp.status === 401 && _authRefresh) {
|
||||||
const ok = await auth.refresh();
|
const ok = await auth.refresh();
|
||||||
if (ok) {
|
if (ok) {
|
||||||
@@ -2575,12 +2554,11 @@
|
|||||||
if (!resp.ok) throw new Error(data.error || data.message || 'HTTP ' + resp.status);
|
if (!resp.ok) throw new Error(data.error || data.message || 'HTTP ' + resp.status);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
// Re-point api methods to patched apiFetch
|
api.get = url => _apiFetchAuth('GET', url);
|
||||||
api.get = url => apiFetch('GET', url);
|
api.post = (u, b) => _apiFetchAuth('POST', u, b);
|
||||||
api.post = (u, b) => apiFetch('POST', u, b);
|
api.put = (u, b) => _apiFetchAuth('PUT', u, b);
|
||||||
api.put = (u, b) => apiFetch('PUT', u, b);
|
api.patch = (u, b) => _apiFetchAuth('PATCH', u, b);
|
||||||
api.patch = (u, b) => apiFetch('PATCH', u, b);
|
api.delete = (u, b) => _apiFetchAuth('DELETE', u, b);
|
||||||
api.delete = (u, b) => apiFetch('DELETE', u, b);
|
|
||||||
|
|
||||||
/* ================================================================
|
/* ================================================================
|
||||||
MODULE 54 — MARKDOWN RENDERER
|
MODULE 54 — MARKDOWN RENDERER
|
||||||
|
|||||||
Reference in New Issue
Block a user