webnode
This commit is contained in:
185
webnode/views/episode.ejs
Normal file
185
webnode/views/episode.ejs
Normal file
@@ -0,0 +1,185 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title><%= currentEpisode.title %></title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar">
|
||||
<button class="nav-btn" id="translateBtn" title="Toggle Translation">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke-width="2">
|
||||
<path d="M12 20h9"/>
|
||||
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="nav-title"><%= currentEpisode.title %></span>
|
||||
<button class="nav-btn" id="menuBtn" title="Episodes">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke-width="2">
|
||||
<path d="M3 12h18"/>
|
||||
<path d="M3 6h18"/>
|
||||
<path d="M3 18h18"/>
|
||||
</svg>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="lines-container">
|
||||
<% if (lines && lines.length > 0) { %>
|
||||
<% lines.forEach(line => { %>
|
||||
<div class="line"
|
||||
data-chinese="<%= escapeHtml(line.chinese) %>"
|
||||
data-timestamp="<%= escapeHtml(line.timestamp) %>"
|
||||
data-speaker="<%= escapeHtml(line.speaker) %>">
|
||||
<div class="english" style="<%= colors[line.speaker] ? `color: ${colors[line.speaker]}` : '' %>">
|
||||
<%= line.english %>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
<% } else { %>
|
||||
<div class="empty-state">
|
||||
<p>No lines found in this episode.</p>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Side Menu Overlay -->
|
||||
<div class="side-menu-overlay" id="sideMenuOverlay"></div>
|
||||
|
||||
<!-- Side Menu -->
|
||||
<aside class="side-menu" id="sideMenu">
|
||||
<div class="side-menu-header">
|
||||
<span class="side-menu-title">Episodes</span>
|
||||
<button class="close-btn" id="closeMenuBtn">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke-width="2">
|
||||
<path d="M18 6L6 18"/>
|
||||
<path d="M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="episode-list">
|
||||
<% episodes.forEach(ep => { %>
|
||||
<a href="/episode/<%= ep.id %>"
|
||||
class="episode-item <%= currentEpisode.id === ep.id ? 'active' : '' %>">
|
||||
<span class="episode-id"><%= ep.title %></span>
|
||||
</a>
|
||||
<% }) %>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Minimal JavaScript for interactions -->
|
||||
<script>
|
||||
(function() {
|
||||
// DOM Elements
|
||||
const translateBtn = document.getElementById('translateBtn');
|
||||
const menuBtn = document.getElementById('menuBtn');
|
||||
const closeMenuBtn = document.getElementById('closeMenuBtn');
|
||||
const sideMenu = document.getElementById('sideMenu');
|
||||
const sideMenuOverlay = document.getElementById('sideMenuOverlay');
|
||||
const lines = document.querySelectorAll('.line');
|
||||
|
||||
let selectedLine = null;
|
||||
let expandedLine = null;
|
||||
|
||||
// Line selection
|
||||
lines.forEach(line => {
|
||||
line.addEventListener('click', function(e) {
|
||||
// Remove selected class from all lines
|
||||
lines.forEach(l => l.classList.remove('selected'));
|
||||
|
||||
// Add selected class to clicked line
|
||||
this.classList.add('selected');
|
||||
selectedLine = this;
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle translation on selected line
|
||||
translateBtn.addEventListener('click', function() {
|
||||
if (!selectedLine) {
|
||||
// If no line selected, select the first one
|
||||
if (lines.length > 0) {
|
||||
lines[0].classList.add('selected');
|
||||
selectedLine = lines[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedLine) {
|
||||
const translationCard = selectedLine.querySelector('.translation-card');
|
||||
|
||||
if (translationCard) {
|
||||
// If already expanded, collapse it
|
||||
translationCard.remove();
|
||||
expandedLine = null;
|
||||
} else {
|
||||
// Collapse any previously expanded line
|
||||
if (expandedLine) {
|
||||
const prevCard = expandedLine.querySelector('.translation-card');
|
||||
if (prevCard) prevCard.remove();
|
||||
}
|
||||
|
||||
// Expand the selected line
|
||||
const chineseText = selectedLine.dataset.chinese;
|
||||
const timestamp = selectedLine.dataset.timestamp;
|
||||
const speaker = selectedLine.dataset.speaker;
|
||||
if (chineseText) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'translation-card';
|
||||
card.innerHTML = `
|
||||
<div class="meta-info">
|
||||
<span class="meta-timestamp">${escapeHtml(timestamp)}</span>
|
||||
<span class="meta-speaker">${escapeHtml(speaker)}</span>
|
||||
</div>
|
||||
<div class="translation-label">中文</div>
|
||||
<div class="chinese">${escapeHtml(chineseText)}</div>
|
||||
`;
|
||||
selectedLine.appendChild(card);
|
||||
expandedLine = selectedLine;
|
||||
|
||||
// Scroll the expanded card into view smoothly
|
||||
setTimeout(() => {
|
||||
card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Open side menu
|
||||
function openMenu() {
|
||||
sideMenu.classList.add('active');
|
||||
sideMenuOverlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
// Close side menu
|
||||
function closeMenu() {
|
||||
sideMenu.classList.remove('active');
|
||||
sideMenuOverlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
menuBtn.addEventListener('click', openMenu);
|
||||
closeMenuBtn.addEventListener('click', closeMenu);
|
||||
sideMenuOverlay.addEventListener('click', closeMenu);
|
||||
|
||||
// Helper: Escape HTML to prevent XSS
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Close menu on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user