Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added debug.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ <h1 class="brand-title">Ruh Al Tarikh</h1>
<i class="fas fa-bookmark" aria-hidden="true"></i>
<span class="badge" id="watchLaterBadge">0</span>
</button>
<button id="dashboardBtn" class="action-btn" aria-label="View Dashboard" title="View Dashboard">
<button id="dashboardBtn" class="action-btn" aria-label="View Dashboard" title="Dashboard (D)" aria-controls="dashboardModal" aria-expanded="false">
<i class="fas fa-chart-line" aria-hidden="true"></i>
</button>
<a href="https://www.youtube.com/@Ruh-Al-Tarikh" target="_blank" rel="noopener noreferrer" class="action-btn" aria-label="Visit YouTube Channel" title="Visit YouTube Channel">
Expand Down
104 changes: 13 additions & 91 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,6 @@ const DOM = {
themeMenu: document.getElementById('themeMenu'),
episodesNavBtn: document.querySelector('[data-action="scroll-to-episodes"]'),
episodesSection: document.getElementById('episodesSection'),
episodesNavBtn: document.querySelector('[data-action="scroll-to-episodes"]'),
episodesSection: document.getElementById('episodesSection'),
episodesNavBtn: document.querySelector('[data-action="scroll-to-episodes"]'),
episodesSection: document.getElementById('episodesSection'),
episodesNavBtn: document.querySelector('[data-action="scroll-to-episodes"]'),
episodesSection: document.getElementById('episodesSection'),
episodesNavBtn: document.querySelector('[data-action="scroll-to-episodes"]'),
episodesSection: document.getElementById('episodesSection'),
episodesNavBtn: document.querySelector('[data-action="scroll-to-episodes"]'),
episodesSection: document.getElementById('episodesSection'),
episodesNavBtn: document.querySelector('[data-action="scroll-to-episodes"]'),
episodesSection: document.getElementById('episodesSection'),
episodesNavBtn: document.querySelector('[data-action="scroll-to-episodes"]'),
episodesSection: document.getElementById('episodesSection'),
menuToggle: document.getElementById('menuToggleBtn'),
scrollToTop: document.getElementById('scrollToTop'),

Expand Down Expand Up @@ -750,6 +736,7 @@ function openDashboard() {
initAIAssistant();
DOM.dashboardModal.style.display = 'block';
DOM.dashboardModal.setAttribute('aria-hidden', 'false');
if (DOM.dashboardBtn) DOM.dashboardBtn.setAttribute('aria-expanded', 'true');
Utils.trapFocus(DOM.dashboardModal);
DOM.body.style.overflow = 'hidden';
DOM.body.classList.add('modal-open');
Expand All @@ -759,6 +746,7 @@ function closeDashboard() {
if (!DOM.dashboardModal) return;
DOM.dashboardModal.style.display = "none";
DOM.dashboardModal.setAttribute("aria-hidden", "true");
if (DOM.dashboardBtn) DOM.dashboardBtn.setAttribute('aria-expanded', 'false');
DOM.body.style.overflow = "";
DOM.body.classList.remove("modal-open");
if (AppState.lastFocused) { AppState.lastFocused.focus(); AppState.lastFocused = null; }
Expand Down Expand Up @@ -1042,83 +1030,6 @@ function bindEvents() {
});
}

// Navbar Episodes Scroll
if (DOM.episodesNavBtn && DOM.episodesSection) {
DOM.episodesNavBtn.addEventListener('click', () => {
DOM.episodesSection.scrollIntoView({ behavior: 'smooth' });
if (document.body.classList.contains('mobile-nav-active')) {
document.body.classList.remove('mobile-nav-active');
if (DOM.menuToggle) DOM.menuToggle.setAttribute('aria-expanded', 'false');
}
});
}

// Navbar Episodes Scroll
if (DOM.episodesNavBtn && DOM.episodesSection) {
DOM.episodesNavBtn.addEventListener('click', () => {
DOM.episodesSection.scrollIntoView({ behavior: 'smooth' });
if (document.body.classList.contains('mobile-nav-active')) {
document.body.classList.remove('mobile-nav-active');
if (DOM.menuToggle) DOM.menuToggle.setAttribute('aria-expanded', 'false');
}
});
}

// Navbar Episodes Scroll
if (DOM.episodesNavBtn && DOM.episodesSection) {
DOM.episodesNavBtn.addEventListener('click', () => {
DOM.episodesSection.scrollIntoView({ behavior: 'smooth' });
if (document.body.classList.contains('mobile-nav-active')) {
document.body.classList.remove('mobile-nav-active');
if (DOM.menuToggle) DOM.menuToggle.setAttribute('aria-expanded', 'false');
}
});
}

// Navbar Episodes Scroll
if (DOM.episodesNavBtn && DOM.episodesSection) {
DOM.episodesNavBtn.addEventListener('click', () => {
DOM.episodesSection.scrollIntoView({ behavior: 'smooth' });
if (document.body.classList.contains('mobile-nav-active')) {
document.body.classList.remove('mobile-nav-active');
if (DOM.menuToggle) DOM.menuToggle.setAttribute('aria-expanded', 'false');
}
});
}

// Navbar Episodes Scroll
if (DOM.episodesNavBtn && DOM.episodesSection) {
DOM.episodesNavBtn.addEventListener('click', () => {
DOM.episodesSection.scrollIntoView({ behavior: 'smooth' });
if (document.body.classList.contains('mobile-nav-active')) {
document.body.classList.remove('mobile-nav-active');
if (DOM.menuToggle) DOM.menuToggle.setAttribute('aria-expanded', 'false');
}
});
}

// Navbar Episodes Scroll
if (DOM.episodesNavBtn && DOM.episodesSection) {
DOM.episodesNavBtn.addEventListener('click', () => {
DOM.episodesSection.scrollIntoView({ behavior: 'smooth' });
if (document.body.classList.contains('mobile-nav-active')) {
document.body.classList.remove('mobile-nav-active');
if (DOM.menuToggle) DOM.menuToggle.setAttribute('aria-expanded', 'false');
}
});
}

// Navbar Episodes Scroll
if (DOM.episodesNavBtn && DOM.episodesSection) {
DOM.episodesNavBtn.addEventListener('click', () => {
DOM.episodesSection.scrollIntoView({ behavior: 'smooth' });
if (document.body.classList.contains('mobile-nav-active')) {
document.body.classList.remove('mobile-nav-active');
if (DOM.menuToggle) DOM.menuToggle.setAttribute('aria-expanded', 'false');
}
});
}

if (DOM.heroBtn) DOM.heroBtn.addEventListener('click', () => AppState.hero && openVideo(AppState.hero));
if (DOM.heroSave) DOM.heroSave.addEventListener('click', () => AppState.hero && toggleWatchLater(AppState.hero));

Expand Down Expand Up @@ -1332,6 +1243,17 @@ function bindEvents() {
toggleTheme();
}

// Dashboard toggle
if (key === 'd') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore modifiers for dashboard shortcut

When focus is anywhere except an input/textarea, this treats every key event whose key is d as the dashboard toggle, including Ctrl+D/Cmd+D that users use to bookmark the page. In that scenario the dashboard opens or closes unexpectedly while the browser bookmark shortcut is invoked; check e.ctrlKey/e.metaKey/e.altKey before handling the new global shortcut.

Useful? React with 👍 / 👎.

if (DOM.dashboardModal) {
if (DOM.dashboardModal.style.display === 'block') {
closeDashboard();
} else {
openDashboard();
}
}
}

// Watch Later toggle
if (key === 'b') {
if (AppState.current) {
Expand Down
53 changes: 53 additions & 0 deletions tests/manual_verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const { chromium } = require('playwright-core');

(async () => {
const browser = await chromium.launch({ executablePath: '/usr/bin/google-chrome' });
const page = await browser.newPage();
await page.goto('http://localhost:8000');

console.log('Testing accessibility attributes...');
const btn = await page.$('#dashboardBtn');
const ariaControls = await btn.getAttribute('aria-controls');
const ariaExpanded = await btn.getAttribute('aria-expanded');
const title = await btn.getAttribute('title');

console.log('aria-controls:', ariaControls);
console.log('aria-expanded:', ariaExpanded);
console.log('title:', title);

if (ariaControls !== 'dashboardModal' || ariaExpanded !== 'false' || title !== 'Dashboard (D)') {
console.error('FAILED accessibility check');
process.exit(1);
}

console.log('Testing keyboard shortcut D to open...');
await page.keyboard.press('d');
const ariaExpandedOpen = await btn.getAttribute('aria-expanded');
console.log('aria-expanded (open):', ariaExpandedOpen);
if (ariaExpandedOpen !== 'true') {
console.error('FAILED keyboard open');
process.exit(1);
}

console.log('Testing keyboard shortcut D to close...');
await page.keyboard.press('d');
const ariaExpandedClosed = await btn.getAttribute('aria-expanded');
console.log('aria-expanded (closed):', ariaExpandedClosed);
if (ariaExpandedClosed !== 'false') {
console.error('FAILED keyboard close');
process.exit(1);
}

console.log('Testing Escape key to close...');
await page.keyboard.press('d');
await page.keyboard.press('Escape');
const ariaExpandedEsc = await btn.getAttribute('aria-expanded');
console.log('aria-expanded (esc):', ariaExpandedEsc);
if (ariaExpandedEsc !== 'false') {
console.error('FAILED escape close');
process.exit(1);
}

console.log('ALL MANUAL CHECKS PASSED');
await browser.close();
})();
41 changes: 41 additions & 0 deletions tests/manual_verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import asyncio
from playwright.async_api import async_playwright

async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
page.on("console", lambda msg: print(f"BROWSER CONSOLE: {msg.text}"))

await page.goto('http://localhost:8000')
await asyncio.sleep(2)

btn = page.locator('#dashboardBtn')

print('Testing click to open...')
await btn.click()
await asyncio.sleep(1)
aria_expanded_open = await btn.get_attribute('aria-expanded')
print(f'aria-expanded (open) after click: {aria_expanded_open}')

if aria_expanded_open == 'true':
await page.screenshot(path="/home/jules/verification/dashboard_open_click.png")
print("Successfully opened dashboard via click")
else:
print("Failed to open dashboard via click")

print('Testing keyboard shortcut d...')
# Close it first if open
if aria_expanded_open == 'true':
await page.keyboard.press('Escape')
await asyncio.sleep(1)

await page.keyboard.press('d')
await asyncio.sleep(1)
aria_expanded_d = await btn.get_attribute('aria-expanded')
print(f'aria-expanded after d: {aria_expanded_d}')

await browser.close()

if __name__ == '__main__':
asyncio.run(main())
67 changes: 67 additions & 0 deletions tests/palette_verify.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { test, expect } from '@playwright/test';

test.describe('Dashboard UX Enhancement', () => {
test.beforeEach(async ({ page }) => {
// Point to the local index.html using the local server
await page.goto('http://localhost:8000');
});

test('Dashboard button has correct accessibility attributes', async ({ page }) => {
const dashboardBtn = page.locator('#dashboardBtn');
await expect(dashboardBtn).toHaveAttribute('aria-controls', 'dashboardModal');
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'false');
await expect(dashboardBtn).toHaveAttribute('title', 'Dashboard (D)');
});

test('Dashboard toggles via button click', async ({ page }) => {
const dashboardBtn = page.locator('#dashboardBtn');
const dashboardModal = page.locator('#dashboardModal');

// Initially closed
await expect(dashboardModal).not.toBeVisible();
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'false');

// Click to open
await dashboardBtn.click();

// Check if dashboard is visible.
// Note: Side panels might be display: block but off-screen.
// However, app.js sets display to 'block' and aria-hidden to 'false'.
await expect(dashboardModal).toHaveAttribute('aria-hidden', 'false');
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'true');

// Close via close button in panel
const closeBtn = page.locator('#closeDashboard');
await closeBtn.click();
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'false');
});

test('Dashboard toggles via keyboard shortcut D', async ({ page }) => {
const dashboardBtn = page.locator('#dashboardBtn');
const dashboardModal = page.locator('#dashboardModal');

// Initially closed
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'false');

// Press 'd' to open
await page.keyboard.press('d');
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'true');
await expect(dashboardModal).toHaveAttribute('aria-hidden', 'false');

// Press 'd' again to close
await page.keyboard.press('d');
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'false');
});

test('Dashboard closes via Escape key', async ({ page }) => {
const dashboardBtn = page.locator('#dashboardBtn');

// Open it
await page.keyboard.press('d');
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'true');

// Press Escape
await page.keyboard.press('Escape');
await expect(dashboardBtn).toHaveAttribute('aria-expanded', 'false');
});
});
Loading