refresh redesign

This commit is contained in:
MDeeApp
2025-10-24 23:28:22 +02:00
parent c7cb02cf2d
commit 6ef62f069c
2 changed files with 372 additions and 21 deletions

View File

@@ -34,6 +34,11 @@ let currentProfile = 1;
let currentTab = 'pending';
let posts = [];
let profilePollTimer = null;
const UPDATES_RECONNECT_DELAY = 5000;
let updatesEventSource = null;
let updatesReconnectTimer = null;
let updatesStreamHealthy = false;
let updatesShouldResyncOnConnect = false;
const MAX_PROFILES = 5;
const PROFILE_NAMES = {
@@ -57,6 +62,153 @@ function apiFetch(url, options = {}) {
return fetch(url, config);
}
function sortPostsByCreatedAt() {
posts.sort((a, b) => toTimestamp(b.created_at, 0) - toTimestamp(a.created_at, 0));
}
function applyPostUpdateFromStream(post) {
if (!post || !post.id) {
return;
}
const index = posts.findIndex((item) => item.id === post.id);
if (index !== -1) {
posts[index] = post;
} else {
posts.push(post);
}
sortPostsByCreatedAt();
if (manualPostMode === 'edit' && manualPostEditingId === post.id) {
populateManualPostForm(post);
}
renderPosts();
}
function removePostFromCache(postId) {
if (!postId) {
return;
}
const index = posts.findIndex((item) => item.id === postId);
if (index === -1) {
return;
}
posts.splice(index, 1);
if (
manualPostMode === 'edit'
&& manualPostEditingId === postId
&& manualPostModal
&& manualPostModal.classList.contains('open')
) {
closeManualPostModal();
}
renderPosts();
}
function handleBackendEvent(eventPayload) {
if (!eventPayload || typeof eventPayload !== 'object') {
return;
}
switch (eventPayload.type) {
case 'post-upsert':
if (eventPayload.post) {
applyPostUpdateFromStream(eventPayload.post);
}
break;
case 'post-deleted':
if (eventPayload.postId) {
removePostFromCache(eventPayload.postId);
}
break;
case 'connected':
case 'heartbeat':
default:
break;
}
}
function scheduleUpdatesReconnect() {
if (updatesReconnectTimer) {
return;
}
updatesReconnectTimer = setTimeout(() => {
updatesReconnectTimer = null;
startUpdatesStream();
}, UPDATES_RECONNECT_DELAY);
}
function startUpdatesStream() {
if (typeof EventSource === 'undefined') {
console.warn('EventSource wird von diesem Browser nicht unterstützt. Fallback auf Polling.');
return;
}
if (updatesEventSource) {
return;
}
const eventsUrl = `${API_URL}/events`;
let eventSource;
try {
eventSource = new EventSource(eventsUrl, { withCredentials: true });
} catch (error) {
console.warn('Konnte Update-Stream nicht starten:', error);
scheduleUpdatesReconnect();
return;
}
updatesEventSource = eventSource;
eventSource.addEventListener('open', () => {
updatesStreamHealthy = true;
if (updatesReconnectTimer) {
clearTimeout(updatesReconnectTimer);
updatesReconnectTimer = null;
}
if (updatesShouldResyncOnConnect) {
updatesShouldResyncOnConnect = false;
fetchPosts({ showLoader: false });
}
applyAutoRefreshSettings();
});
eventSource.addEventListener('message', (event) => {
if (!event || typeof event.data !== 'string' || !event.data.trim()) {
return;
}
let payload;
try {
payload = JSON.parse(event.data);
} catch (error) {
console.warn('Ungültige Daten vom Update-Stream erhalten:', error);
return;
}
handleBackendEvent(payload);
});
eventSource.addEventListener('error', () => {
if (updatesEventSource) {
updatesEventSource.close();
updatesEventSource = null;
}
if (!updatesShouldResyncOnConnect) {
updatesShouldResyncOnConnect = true;
}
updatesStreamHealthy = false;
applyAutoRefreshSettings();
fetchPosts({ showLoader: false });
scheduleUpdatesReconnect();
});
}
const screenshotModal = document.getElementById('screenshotModal');
const screenshotModalContent = document.getElementById('screenshotModalContent');
const screenshotModalImage = document.getElementById('screenshotModalImage');
@@ -131,7 +283,7 @@ function initializeFocusParams() {
let autoRefreshTimer = null;
let autoRefreshSettings = {
enabled: true,
enabled: false,
interval: 30000
};
let sortMode = DEFAULT_SORT_SETTINGS.mode;
@@ -2106,10 +2258,22 @@ function applyAutoRefreshSettings() {
autoRefreshTimer = null;
}
if (autoRefreshIntervalSelect) {
const disabled = !autoRefreshSettings.enabled || updatesStreamHealthy;
autoRefreshIntervalSelect.disabled = disabled;
autoRefreshIntervalSelect.title = updatesStreamHealthy
? 'Live-Updates sind aktiv; das Intervall wird aktuell nicht verwendet.'
: '';
}
if (!autoRefreshSettings.enabled) {
return;
}
if (updatesStreamHealthy) {
return;
}
autoRefreshTimer = setInterval(() => {
if (document.hidden) {
return;
@@ -2485,12 +2649,12 @@ if (manualPostResetButton) {
if (autoRefreshToggle) {
autoRefreshToggle.addEventListener('change', () => {
autoRefreshSettings.enabled = !!autoRefreshToggle.checked;
if (autoRefreshIntervalSelect) {
autoRefreshIntervalSelect.disabled = !autoRefreshSettings.enabled;
}
saveAutoRefreshSettings();
applyAutoRefreshSettings();
if (autoRefreshSettings.enabled) {
if (autoRefreshSettings.enabled && updatesStreamHealthy) {
console.info('Live-Updates sind aktiv; automatisches Refresh bleibt pausiert.');
}
if (autoRefreshSettings.enabled && !updatesStreamHealthy) {
fetchPosts({ showLoader: false });
}
});
@@ -2578,6 +2742,7 @@ async function fetchPosts({ showLoader = true } = {}) {
const data = await response.json();
posts = Array.isArray(data) ? data : [];
await normalizeLoadedPostUrls();
sortPostsByCreatedAt();
renderPosts();
} catch (error) {
if (showLoader) {
@@ -3068,15 +3233,20 @@ function createPostCard(post, status, meta = {}) {
const createdDate = formatDateTime(post.created_at) || '—';
const lastChangeDate = formatDateTime(post.last_change || post.created_at) || '—';
const resolvedScreenshotPath = post.screenshot_path
const baseScreenshotPath = post.screenshot_path
? (post.screenshot_path.startsWith('http')
? post.screenshot_path
: `${API_URL.replace(/\/api$/, '')}${post.screenshot_path.startsWith('/') ? '' : '/'}${post.screenshot_path}`)
: `${API_URL}/posts/${post.id}/screenshot`;
const screenshotVersion = post.last_change || post.updated_at || post.created_at || '';
const versionedScreenshotPath = screenshotVersion
? `${baseScreenshotPath}${baseScreenshotPath.includes('?') ? '&' : '?'}v=${encodeURIComponent(screenshotVersion)}`
: baseScreenshotPath;
const resolvedScreenshotPath = versionedScreenshotPath;
const screenshotHtml = `
<div class="post-screenshot" data-screenshot="${escapeHtml(resolvedScreenshotPath)}" role="button" tabindex="0" aria-label="Screenshot anzeigen">
<img src="${escapeHtml(resolvedScreenshotPath)}" alt="Screenshot zum Beitrag" loading="lazy" />
<div class="post-screenshot" data-screenshot="${escapeHtml(versionedScreenshotPath)}" role="button" tabindex="0" aria-label="Screenshot anzeigen">
<img src="${escapeHtml(versionedScreenshotPath)}" alt="Screenshot zum Beitrag" loading="lazy" />
</div>
`;
@@ -3862,4 +4032,5 @@ loadProfile();
startProfilePolling();
fetchPosts();
checkAutoCheck();
startUpdatesStream();
applyAutoRefreshSettings();