Files
bytechat-desktop/src/rss.js
rattatwinko 4d9346219d
All checks were successful
Build Tauri App (Linux + Windows exe) / build (push) Successful in 11m34s
General Overhaul of the Frontend + Functionallity for Room Searching
2025-08-31 23:49:04 +02:00

262 lines
10 KiB
JavaScript

async function loadRSS() {
const container = document.getElementById("rss-feed");
const showLegacyContainer = document.getElementById("show-legacy-container");
const showLegacyBtn = document.getElementById("show-legacy-btn");
if (!container) {
console.error("RSS feed container not found");
return;
}
// Show loading state
container.innerHTML = "<p>Loading release notes...</p>";
const rssUrl = "https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/bytechat-desktop/releases.rss";
try {
// Try direct fetch first
let response;
let xmlText;
try {
response = await fetch(rssUrl, {
method: 'GET',
headers: {
'Accept': 'application/rss+xml, application/xml, text/xml, */*',
'Cache-Control': 'no-cache'
},
mode: 'cors'
});
if (response.ok) {
xmlText = await response.text();
} else {
throw new Error(`HTTP ${response.status}`);
}
} catch (corsError) {
console.warn("Direct fetch failed, trying proxy...", corsError);
// Try CORS proxy
const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(rssUrl)}`;
const proxyResponse = await fetch(proxyUrl);
if (!proxyResponse.ok) {
throw new Error(`Proxy failed: ${proxyResponse.status}`);
}
const proxyData = await proxyResponse.json();
if (!proxyData.contents) {
throw new Error("No content from proxy");
}
xmlText = proxyData.contents;
}
// Parse the XML
const parser = new DOMParser();
const xml = parser.parseFromString(xmlText, "application/xml");
// Check for parsing errors
const parserError = xml.querySelector("parsererror");
if (parserError) {
throw new Error("XML parsing failed");
}
const items = parseRSSItems(xml);
displayRSSItems(items, container, showLegacyContainer, showLegacyBtn);
} catch (err) {
console.error("Failed to load RSS:", err);
// Show error with retry and direct link
container.innerHTML = `
<div style="padding: 1rem; background: rgba(255, 100, 100, 0.1); border: 1px solid rgba(255, 100, 100, 0.3); border-radius: 6px; text-align: center;">
<p style="margin: 0 0 0.5rem 0;"><strong>Unable to load release notes</strong></p>
<p style="font-size: 0.9em; margin: 0.5rem 0; color: #ccc;">
Network issue detected.
</p>
<div style="display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; margin-top: 1rem;">
<button onclick="loadRSS()" style="padding: 0.4rem 0.8rem; background: #00ff88; color: black; border: none; border-radius: 4px; cursor: pointer; font-weight: 500;">
Retry
</button>
<a href="${rssUrl}" target="_blank" rel="noopener" style="padding: 0.4rem 0.8rem; background: #333; color: #00ff88; text-decoration: none; border-radius: 4px; display: inline-block;">
View Direct
</a>
</div>
</div>
`;
}
}
function parseRSSItems(xml) {
console.log("Parsing RSS XML...");
const items = Array.from(xml.querySelectorAll("item")).map(item => {
const title = item.querySelector("title")?.textContent?.trim() || "Untitled Release";
const link = item.querySelector("link")?.textContent?.trim() || "#";
const pubDate = item.querySelector("pubDate")?.textContent?.trim() || "";
const author = item.querySelector("author")?.textContent?.trim() || "Unknown";
const description = item.querySelector("description")?.textContent?.trim() || "";
// Extract content from CDATA if available
const contentEncoded = item.querySelector("content\\:encoded");
let content = "";
if (contentEncoded) {
content = contentEncoded.textContent.trim();
// Remove HTML tags for a clean preview
content = content.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
// Limit to first 150 characters
if (content.length > 150) {
content = content.substring(0, 150) + "...";
}
}
console.log("Parsed item:", { title, pubDate, author });
return { title, link, pubDate, author, description, content };
});
console.log(`Found ${items.length} releases`);
// Sort by date (newest first)
return items.sort((a, b) => {
if (!a.pubDate || !b.pubDate) return 0;
return new Date(b.pubDate) - new Date(a.pubDate);
});
}
function displayRSSItems(items, container, showLegacyContainer, showLegacyBtn) {
if (!items.length) {
container.innerHTML = "<p style='text-align: center; color: #888;'>No releases found.</p>";
return;
}
console.log("Displaying items:", items);
const visibleItems = 2;
const hasMoreItems = items.length > visibleItems;
let html = "<div class='release-list' style='display: flex; flex-direction: column; gap: 1rem;'>";
items.forEach((item, index) => {
const isHidden = index >= visibleItems;
// Format the date nicely
let formattedDate = "";
if (item.pubDate) {
try {
const date = new Date(item.pubDate);
formattedDate = date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch (e) {
formattedDate = item.pubDate;
}
}
html += `
<div class="release-item ${isHidden ? 'legacy-release' : ''}"
style="
${isHidden ? 'display: none;' : ''}
padding: 1rem;
background: linear-gradient(135deg, rgba(0, 255, 136, 0.05) 0%, rgba(0, 255, 136, 0.02) 100%);
border: 1px solid rgba(0, 255, 136, 0.2);
border-radius: 8px;
border-left: 4px solid #00ff88;
transition: all 0.3s ease;
"
onmouseover="this.style.background='linear-gradient(135deg, rgba(0, 255, 136, 0.08) 0%, rgba(0, 255, 136, 0.04) 100%)'; this.style.transform='translateY(-1px)'"
onmouseout="this.style.background='linear-gradient(135deg, rgba(0, 255, 136, 0.05) 0%, rgba(0, 255, 136, 0.02) 100%)'; this.style.transform='translateY(0px)'">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem; flex-wrap: wrap; gap: 0.5rem;">
<h4 style="margin: 0; color: #00ff88; font-size: 1.1rem; font-weight: 600;">
<a href="${item.link}"
target="_blank"
rel="noopener"
style="color: inherit; text-decoration: none;"
onmouseover="this.style.textDecoration='underline'"
onmouseout="this.style.textDecoration='none'">
${item.title}
</a>
</h4>
${formattedDate ? `
<span style="color: #888; font-size: 0.85rem; white-space: nowrap;">
${formattedDate}
</span>
` : ''}
</div>
${item.author !== 'Unknown' ? `
<div style="color: #666; font-size: 0.8rem; margin-bottom: 0.5rem;">
by ${item.author}
</div>
` : ''}
${item.content ? `
<div style="color: #ccc; font-size: 0.9rem; line-height: 1.4; margin-top: 0.5rem;">
${item.content}
</div>
` : ''}
</div>
`;
});
html += "</div>";
container.innerHTML = html;
// Setup legacy releases toggle
if (hasMoreItems && showLegacyContainer && showLegacyBtn) {
showLegacyContainer.style.display = 'block';
// Remove existing listeners by cloning
const newBtn = showLegacyBtn.cloneNode(true);
showLegacyBtn.parentNode.replaceChild(newBtn, showLegacyBtn);
let showingLegacy = false;
newBtn.addEventListener('click', function() {
showingLegacy = !showingLegacy;
const legacyItems = container.querySelectorAll('.legacy-release');
legacyItems.forEach(item => {
if (showingLegacy) {
item.style.display = 'block';
item.style.animation = 'fadeIn 0.3s ease-in';
} else {
item.style.display = 'none';
}
});
newBtn.textContent = showingLegacy ?
'Hide Legacy Releases' : `Show ${items.length - visibleItems} More Releases`;
});
// Update button text to show count
newBtn.textContent = `Show ${items.length - visibleItems} More Releases`;
}
console.log("RSS items displayed successfully");
}
// Add some CSS for animations
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
`;
document.head.appendChild(style);
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadRSS);
} else {
// DOM is already loaded
loadRSS();
}
// Make loadRSS available globally for retry button
window.loadRSS = loadRSS;