888 lines
25 KiB
JavaScript
888 lines
25 KiB
JavaScript
const API_URL = 'https://fb.srv.medeba-media.de/api';
|
|
|
|
let currentProfile = 1;
|
|
let currentTab = 'pending';
|
|
let posts = [];
|
|
let profilePollTimer = null;
|
|
|
|
const MAX_PROFILES = 5;
|
|
const PROFILE_NAMES = {
|
|
1: 'Profil 1',
|
|
2: 'Profil 2',
|
|
3: 'Profil 3',
|
|
4: 'Profil 4',
|
|
5: 'Profil 5'
|
|
};
|
|
|
|
const screenshotModal = document.getElementById('screenshotModal');
|
|
const screenshotModalContent = document.getElementById('screenshotModalContent');
|
|
const screenshotModalImage = document.getElementById('screenshotModalImage');
|
|
const screenshotModalClose = document.getElementById('screenshotModalClose');
|
|
const screenshotModalBackdrop = document.getElementById('screenshotModalBackdrop');
|
|
let screenshotModalLastFocus = null;
|
|
let screenshotModalPreviousOverflow = '';
|
|
let screenshotModalZoomed = false;
|
|
|
|
function getProfileName(profileNumber) {
|
|
if (!profileNumber) {
|
|
return '';
|
|
}
|
|
return PROFILE_NAMES[profileNumber] || `Profil ${profileNumber}`;
|
|
}
|
|
|
|
function formatDateTime(value) {
|
|
if (!value) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
const date = value instanceof Date ? value : new Date(value);
|
|
if (Number.isNaN(date.getTime())) {
|
|
return '';
|
|
}
|
|
return date.toLocaleString('de-DE');
|
|
} catch (error) {
|
|
console.warn('Ungültiges Datum:', error);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function formatUrlForDisplay(url) {
|
|
if (!url) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
const parsed = new URL(url);
|
|
const pathname = parsed.pathname === '/' ? '' : parsed.pathname;
|
|
const search = parsed.search || '';
|
|
return `${parsed.hostname}${pathname}${search}`;
|
|
} catch (error) {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
function normalizeRequiredProfiles(post) {
|
|
if (Array.isArray(post.required_profiles) && post.required_profiles.length) {
|
|
return post.required_profiles
|
|
.map((value) => {
|
|
const parsed = parseInt(value, 10);
|
|
if (Number.isNaN(parsed)) {
|
|
return null;
|
|
}
|
|
return Math.min(MAX_PROFILES, Math.max(1, parsed));
|
|
})
|
|
.filter(Boolean);
|
|
}
|
|
|
|
const parsedTarget = parseInt(post.target_count, 10);
|
|
const count = Number.isNaN(parsedTarget)
|
|
? 1
|
|
: Math.min(MAX_PROFILES, Math.max(1, parsedTarget));
|
|
|
|
return Array.from({ length: count }, (_, index) => index + 1);
|
|
}
|
|
|
|
function normalizeChecks(checks) {
|
|
if (!Array.isArray(checks)) {
|
|
return [];
|
|
}
|
|
|
|
return checks
|
|
.map((check) => {
|
|
if (!check) {
|
|
return null;
|
|
}
|
|
|
|
const parsed = parseInt(check.profile_number, 10);
|
|
if (Number.isNaN(parsed)) {
|
|
return null;
|
|
}
|
|
|
|
const profileNumber = Math.min(MAX_PROFILES, Math.max(1, parsed));
|
|
|
|
return {
|
|
...check,
|
|
profile_number: profileNumber,
|
|
profile_name: check.profile_name || getProfileName(profileNumber),
|
|
checked_at: check.checked_at || null
|
|
};
|
|
})
|
|
.filter(Boolean)
|
|
.sort((a, b) => {
|
|
const aTime = a.checked_at ? new Date(a.checked_at).getTime() : 0;
|
|
const bTime = b.checked_at ? new Date(b.checked_at).getTime() : 0;
|
|
if (aTime === bTime) {
|
|
return a.profile_number - b.profile_number;
|
|
}
|
|
return aTime - bTime;
|
|
});
|
|
}
|
|
|
|
function computePostStatus(post, profileNumber = currentProfile) {
|
|
const requiredProfiles = normalizeRequiredProfiles(post);
|
|
const checks = normalizeChecks(post.checks);
|
|
|
|
const backendStatuses = Array.isArray(post.profile_statuses) ? post.profile_statuses : [];
|
|
let profileStatuses = backendStatuses
|
|
.map((status) => {
|
|
if (!status) {
|
|
return null;
|
|
}
|
|
const parsed = parseInt(status.profile_number, 10);
|
|
if (Number.isNaN(parsed)) {
|
|
return null;
|
|
}
|
|
const profileNumberValue = Math.min(MAX_PROFILES, Math.max(1, parsed));
|
|
let normalizedStatus = status.status;
|
|
if (normalizedStatus !== 'done' && normalizedStatus !== 'available') {
|
|
normalizedStatus = 'locked';
|
|
}
|
|
const check = checks.find((item) => item.profile_number === profileNumberValue) || null;
|
|
return {
|
|
profile_number: profileNumberValue,
|
|
profile_name: status.profile_name || getProfileName(profileNumberValue),
|
|
status: normalizedStatus,
|
|
checked_at: status.checked_at || (check ? check.checked_at : null) || null
|
|
};
|
|
})
|
|
.filter(Boolean);
|
|
|
|
if (profileStatuses.length !== requiredProfiles.length) {
|
|
const checksByProfile = new Map(checks.map((check) => [check.profile_number, check]));
|
|
const completedSet = new Set(checks.map((check) => check.profile_number));
|
|
|
|
profileStatuses = requiredProfiles.map((value, index) => {
|
|
const prerequisites = requiredProfiles.slice(0, index);
|
|
const prerequisitesMet = prerequisites.every((profile) => completedSet.has(profile));
|
|
const check = checksByProfile.get(value) || null;
|
|
|
|
return {
|
|
profile_number: value,
|
|
profile_name: getProfileName(value),
|
|
status: check ? 'done' : (prerequisitesMet ? 'available' : 'locked'),
|
|
checked_at: check ? check.checked_at : null
|
|
};
|
|
});
|
|
} else {
|
|
const checksByProfile = new Map(checks.map((check) => [check.profile_number, check]));
|
|
profileStatuses = requiredProfiles.map((value) => {
|
|
const status = profileStatuses.find((item) => item.profile_number === value);
|
|
if (!status) {
|
|
const check = checksByProfile.get(value) || null;
|
|
return {
|
|
profile_number: value,
|
|
profile_name: getProfileName(value),
|
|
status: check ? 'done' : 'locked',
|
|
checked_at: check ? check.checked_at : null
|
|
};
|
|
}
|
|
|
|
if (status.status === 'done') {
|
|
const check = checksByProfile.get(value) || null;
|
|
return {
|
|
...status,
|
|
checked_at: status.checked_at || (check ? check.checked_at : null) || null
|
|
};
|
|
}
|
|
|
|
if (status.status === 'available') {
|
|
return {
|
|
...status,
|
|
checked_at: status.checked_at || null
|
|
};
|
|
}
|
|
|
|
return {
|
|
...status,
|
|
status: 'locked',
|
|
checked_at: status.checked_at || null
|
|
};
|
|
});
|
|
}
|
|
|
|
const completedProfilesSet = new Set(
|
|
profileStatuses
|
|
.filter((status) => status.status === 'done')
|
|
.map((status) => status.profile_number)
|
|
);
|
|
|
|
const checkedCount = profileStatuses.filter((status) => status.status === 'done').length;
|
|
const targetCount = profileStatuses.length;
|
|
const isComplete = profileStatuses.every((status) => status.status === 'done');
|
|
const nextRequiredProfile = profileStatuses.find((status) => status.status === 'available') || null;
|
|
const isCurrentProfileRequired = requiredProfiles.includes(profileNumber);
|
|
const isCurrentProfileDone = profileStatuses.some((status) => status.profile_number === profileNumber && status.status === 'done');
|
|
const canCurrentProfileCheck = profileStatuses.some((status) => status.profile_number === profileNumber && status.status === 'available');
|
|
|
|
const waitingForStatuses = profileStatuses.filter((status) => status.profile_number < profileNumber && status.status !== 'done');
|
|
const waitingForProfiles = waitingForStatuses.map((status) => status.profile_number);
|
|
const waitingForNames = waitingForStatuses.map((status) => status.profile_name);
|
|
|
|
return {
|
|
requiredProfiles,
|
|
profileStatuses,
|
|
checks,
|
|
completedProfilesSet,
|
|
checkedCount,
|
|
targetCount,
|
|
isComplete,
|
|
isCurrentProfileRequired,
|
|
isCurrentProfileDone,
|
|
canCurrentProfileCheck,
|
|
waitingForProfiles,
|
|
waitingForNames,
|
|
nextRequiredProfile,
|
|
profileNumber
|
|
};
|
|
}
|
|
|
|
function applyScreenshotModalSize() {
|
|
if (!screenshotModalContent || !screenshotModalImage) {
|
|
return;
|
|
}
|
|
|
|
if (screenshotModalZoomed) {
|
|
return;
|
|
}
|
|
|
|
if (!screenshotModalImage.src) {
|
|
return;
|
|
}
|
|
|
|
requestAnimationFrame(() => {
|
|
const padding = 48;
|
|
const viewportWidth = Math.max(320, window.innerWidth * 0.95);
|
|
const viewportHeight = Math.max(280, window.innerHeight * 0.92);
|
|
const naturalWidth = screenshotModalImage.naturalWidth || screenshotModalImage.width || 0;
|
|
const naturalHeight = screenshotModalImage.naturalHeight || screenshotModalImage.height || 0;
|
|
|
|
const targetWidth = Math.min(Math.max(320, naturalWidth + padding), viewportWidth);
|
|
const targetHeight = Math.min(Math.max(260, naturalHeight + padding), viewportHeight);
|
|
|
|
screenshotModalContent.style.width = `${targetWidth}px`;
|
|
screenshotModalContent.style.height = `${targetHeight}px`;
|
|
});
|
|
}
|
|
|
|
async function fetchProfileState() {
|
|
try {
|
|
const response = await fetch(`${API_URL}/profile-state`);
|
|
if (!response.ok) {
|
|
return null;
|
|
}
|
|
const data = await response.json();
|
|
if (data && typeof data.profile_number !== 'undefined') {
|
|
const parsed = parseInt(data.profile_number, 10);
|
|
if (!Number.isNaN(parsed)) {
|
|
return parsed;
|
|
}
|
|
}
|
|
return null;
|
|
} catch (error) {
|
|
console.warn('Profilstatus konnte nicht geladen werden:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function pushProfileState(profileNumber) {
|
|
try {
|
|
await fetch(`${API_URL}/profile-state`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ profile_number: profileNumber })
|
|
});
|
|
} catch (error) {
|
|
console.error('Profilstatus konnte nicht gespeichert werden:', error);
|
|
}
|
|
}
|
|
|
|
function applyProfileNumber(profileNumber, { fromBackend = false } = {}) {
|
|
if (!profileNumber) {
|
|
return;
|
|
}
|
|
|
|
document.getElementById('profileSelect').value = String(profileNumber);
|
|
|
|
if (currentProfile === profileNumber) {
|
|
if (!fromBackend) {
|
|
pushProfileState(profileNumber);
|
|
}
|
|
return;
|
|
}
|
|
|
|
currentProfile = profileNumber;
|
|
localStorage.setItem('profileNumber', currentProfile);
|
|
|
|
if (!fromBackend) {
|
|
pushProfileState(currentProfile);
|
|
}
|
|
|
|
renderPosts();
|
|
}
|
|
|
|
// Load profile from localStorage
|
|
function loadProfile() {
|
|
fetchProfileState().then((backendProfile) => {
|
|
if (backendProfile) {
|
|
applyProfileNumber(backendProfile, { fromBackend: true });
|
|
} else {
|
|
const saved = localStorage.getItem('profileNumber');
|
|
if (saved) {
|
|
applyProfileNumber(parseInt(saved, 10) || 1, { fromBackend: true });
|
|
} else {
|
|
applyProfileNumber(1, { fromBackend: true });
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Save profile to localStorage
|
|
function saveProfile(profileNumber) {
|
|
applyProfileNumber(profileNumber);
|
|
}
|
|
|
|
function startProfilePolling() {
|
|
if (profilePollTimer) {
|
|
clearInterval(profilePollTimer);
|
|
}
|
|
|
|
profilePollTimer = setInterval(async () => {
|
|
const backendProfile = await fetchProfileState();
|
|
if (backendProfile && backendProfile !== currentProfile) {
|
|
applyProfileNumber(backendProfile, { fromBackend: true });
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
// Profile selector change handler
|
|
document.getElementById('profileSelect').addEventListener('change', (e) => {
|
|
saveProfile(parseInt(e.target.value, 10));
|
|
});
|
|
|
|
// Tab switching
|
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
currentTab = btn.dataset.tab;
|
|
renderPosts();
|
|
});
|
|
});
|
|
|
|
// Fetch all posts
|
|
async function fetchPosts() {
|
|
try {
|
|
showLoading();
|
|
const response = await fetch(`${API_URL}/posts`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch posts');
|
|
}
|
|
|
|
posts = await response.json();
|
|
renderPosts();
|
|
} catch (error) {
|
|
showError('Fehler beim Laden der Beiträge. Stelle sicher, dass das Backend läuft.');
|
|
console.error('Error fetching posts:', error);
|
|
}
|
|
}
|
|
|
|
// Render posts
|
|
function renderPosts() {
|
|
hideLoading();
|
|
hideError();
|
|
|
|
const container = document.getElementById('postsContainer');
|
|
|
|
const postItems = posts.map((post) => ({
|
|
post,
|
|
status: computePostStatus(post)
|
|
}));
|
|
|
|
let filteredItems = postItems;
|
|
|
|
if (currentTab === 'pending') {
|
|
filteredItems = postItems.filter((item) => item.status.canCurrentProfileCheck && !item.status.isComplete);
|
|
}
|
|
|
|
if (filteredItems.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">🎉</div>
|
|
<div class="empty-state-text">
|
|
${currentTab === 'pending' ? 'Keine offenen Beiträge!' : 'Noch keine Beiträge erfasst.'}
|
|
</div>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = filteredItems
|
|
.map(({ post, status }) => createPostCard(post, status))
|
|
.join('');
|
|
|
|
filteredItems.forEach(({ post, status }) => {
|
|
const card = document.getElementById(`post-${post.id}`);
|
|
if (!card) {
|
|
return;
|
|
}
|
|
|
|
const openBtn = card.querySelector('.btn-open');
|
|
if (openBtn) {
|
|
openBtn.addEventListener('click', () => openPost(post.id));
|
|
}
|
|
|
|
const editBtn = card.querySelector('.btn-edit-target');
|
|
if (editBtn) {
|
|
editBtn.addEventListener('click', () => promptEditTarget(post.id, status.targetCount));
|
|
}
|
|
|
|
const deleteBtn = card.querySelector('.btn-delete');
|
|
if (deleteBtn) {
|
|
deleteBtn.addEventListener('click', () => deletePost(post.id));
|
|
}
|
|
|
|
const screenshotEl = card.querySelector('.post-screenshot');
|
|
if (screenshotEl && screenshotEl.dataset.screenshot) {
|
|
const url = screenshotEl.dataset.screenshot;
|
|
const openHandler = () => openScreenshotModal(url);
|
|
screenshotEl.addEventListener('click', openHandler);
|
|
screenshotEl.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
event.preventDefault();
|
|
openScreenshotModal(url);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function openScreenshotModal(url) {
|
|
if (!screenshotModal || !url) {
|
|
return;
|
|
}
|
|
|
|
screenshotModalLastFocus = document.activeElement;
|
|
screenshotModalImage.src = url;
|
|
resetScreenshotZoom();
|
|
screenshotModalPreviousOverflow = document.body.style.overflow;
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
screenshotModal.removeAttribute('hidden');
|
|
screenshotModal.classList.add('open');
|
|
|
|
if (screenshotModalClose) {
|
|
screenshotModalClose.focus();
|
|
}
|
|
|
|
if (screenshotModalImage.complete) {
|
|
applyScreenshotModalSize();
|
|
} else {
|
|
const handleLoad = () => {
|
|
applyScreenshotModalSize();
|
|
};
|
|
screenshotModalImage.addEventListener('load', handleLoad, { once: true });
|
|
}
|
|
}
|
|
|
|
function closeScreenshotModal() {
|
|
if (!screenshotModal) {
|
|
return;
|
|
}
|
|
|
|
if (!screenshotModal.classList.contains('open')) {
|
|
return;
|
|
}
|
|
|
|
resetScreenshotZoom();
|
|
screenshotModal.classList.remove('open');
|
|
screenshotModal.setAttribute('hidden', '');
|
|
screenshotModalImage.src = '';
|
|
document.body.style.overflow = screenshotModalPreviousOverflow;
|
|
|
|
if (screenshotModalLastFocus && typeof screenshotModalLastFocus.focus === 'function') {
|
|
screenshotModalLastFocus.focus();
|
|
}
|
|
}
|
|
|
|
function resetScreenshotZoom() {
|
|
screenshotModalZoomed = false;
|
|
if (screenshotModalContent) {
|
|
screenshotModalContent.classList.remove('zoomed');
|
|
screenshotModalContent.style.width = '';
|
|
screenshotModalContent.style.height = '';
|
|
screenshotModalContent.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
|
}
|
|
if (screenshotModalImage) {
|
|
screenshotModalImage.classList.remove('zoomed');
|
|
}
|
|
applyScreenshotModalSize();
|
|
}
|
|
|
|
function toggleScreenshotZoom() {
|
|
if (!screenshotModalContent || !screenshotModalImage) {
|
|
return;
|
|
}
|
|
|
|
screenshotModalZoomed = !screenshotModalZoomed;
|
|
screenshotModalContent.classList.toggle('zoomed', screenshotModalZoomed);
|
|
screenshotModalImage.classList.toggle('zoomed', screenshotModalZoomed);
|
|
|
|
if (screenshotModalZoomed) {
|
|
screenshotModalContent.style.width = 'min(95vw, 1300px)';
|
|
screenshotModalContent.style.height = '92vh';
|
|
screenshotModalContent.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
|
} else {
|
|
screenshotModalContent.style.width = '';
|
|
screenshotModalContent.style.height = '';
|
|
applyScreenshotModalSize();
|
|
}
|
|
}
|
|
|
|
// Create post card HTML
|
|
function createPostCard(post, status) {
|
|
const createdDate = formatDateTime(post.created_at) || '—';
|
|
|
|
const resolvedScreenshotPath = post.screenshot_path
|
|
? (post.screenshot_path.startsWith('http')
|
|
? post.screenshot_path
|
|
: `${API_URL.replace(/\/api$/, '')}${post.screenshot_path}`)
|
|
: null;
|
|
|
|
const screenshotHtml = resolvedScreenshotPath
|
|
? `
|
|
<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>
|
|
`
|
|
: '';
|
|
|
|
const profileRowsHtml = status.profileStatuses.map((profileStatus) => {
|
|
const classes = ['profile-line', `profile-line--${profileStatus.status}`];
|
|
let label = 'Wartet';
|
|
if (profileStatus.status === 'done') {
|
|
const doneDate = formatDateTime(profileStatus.checked_at);
|
|
label = doneDate ? `Erledigt (${doneDate})` : 'Erledigt';
|
|
} else if (profileStatus.status === 'available') {
|
|
label = 'Bereit';
|
|
}
|
|
|
|
return `
|
|
<div class="${classes.join(' ')}">
|
|
<span class="profile-line__name">${escapeHtml(profileStatus.profile_name)}</span>
|
|
<span class="profile-line__status">${escapeHtml(label)}</span>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
const infoMessages = [];
|
|
if (!status.isCurrentProfileRequired) {
|
|
infoMessages.push('Dieses Profil muss den Beitrag nicht bestätigen.');
|
|
} else if (status.isCurrentProfileDone) {
|
|
infoMessages.push('Für dein Profil erledigt.');
|
|
} else if (status.waitingForNames.length) {
|
|
infoMessages.push(`Wartet auf: ${status.waitingForNames.join(', ')}`);
|
|
}
|
|
|
|
const infoHtml = infoMessages.length
|
|
? `
|
|
<div class="post-hints">
|
|
${infoMessages.map((message) => `
|
|
<div class="post-hint${message.includes('erledigt') ? ' post-hint--success' : ''}">
|
|
${escapeHtml(message)}
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
`
|
|
: '';
|
|
|
|
const directLinkHtml = post.url
|
|
? `
|
|
<div class="post-link">
|
|
<span class="post-link__label">Direktlink:</span>
|
|
<a href="${escapeHtml(post.url)}" target="_blank" rel="noopener noreferrer" class="post-link__anchor">
|
|
${escapeHtml(formatUrlForDisplay(post.url))}
|
|
</a>
|
|
</div>
|
|
`
|
|
: '';
|
|
|
|
const openButtonHtml = status.canCurrentProfileCheck
|
|
? `
|
|
<button class="btn btn-success btn-open">Beitrag öffnen & abhaken</button>
|
|
`
|
|
: '';
|
|
|
|
return `
|
|
<div class="post-card ${status.isComplete ? 'complete' : ''}" id="post-${post.id}">
|
|
<div class="post-header">
|
|
<div class="post-title">${escapeHtml(post.title || '')}</div>
|
|
<div class="post-status ${status.isComplete ? 'complete' : ''}">
|
|
${status.checkedCount}/${status.targetCount} ${status.isComplete ? '✓' : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="post-meta">
|
|
<div class="post-info">Erstellt: ${escapeHtml(createdDate)}</div>
|
|
<div class="post-target">
|
|
<span>Benötigte Profile: ${status.targetCount}</span>
|
|
<button type="button" class="btn btn-inline btn-edit-target">Anzahl ändern</button>
|
|
</div>
|
|
</div>
|
|
|
|
${directLinkHtml}
|
|
|
|
<div class="post-profiles">
|
|
${profileRowsHtml}
|
|
</div>
|
|
|
|
${infoHtml}
|
|
|
|
${screenshotHtml}
|
|
|
|
<div class="post-actions">
|
|
${openButtonHtml}
|
|
${post.url ? `
|
|
<a href="${escapeHtml(post.url)}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary btn-direct-link">
|
|
Direkt öffnen
|
|
</a>
|
|
` : ''}
|
|
<button class="btn btn-danger btn-delete">Löschen</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Open post and auto-check
|
|
async function openPost(postId) {
|
|
const post = posts.find((item) => item.id === postId);
|
|
if (!post) {
|
|
alert('Beitrag konnte nicht gefunden werden.');
|
|
return;
|
|
}
|
|
|
|
if (!post.url) {
|
|
alert('Für diesen Beitrag ist kein Direktlink vorhanden.');
|
|
return;
|
|
}
|
|
|
|
const status = computePostStatus(post);
|
|
|
|
if (!status.isCurrentProfileRequired) {
|
|
alert('Dieses Profil muss den Beitrag nicht bestätigen.');
|
|
return;
|
|
}
|
|
|
|
if (status.isCurrentProfileDone) {
|
|
window.open(post.url, '_blank');
|
|
return;
|
|
}
|
|
|
|
if (!status.canCurrentProfileCheck) {
|
|
if (status.waitingForNames.length) {
|
|
alert(`Wartet auf: ${status.waitingForNames.join(', ')}`);
|
|
} else {
|
|
alert('Der Beitrag kann aktuell nicht abgehakt werden.');
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/check-by-url`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
url: post.url,
|
|
profile_number: currentProfile
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 409) {
|
|
const data = await response.json().catch(() => null);
|
|
if (data && data.error) {
|
|
alert(data.error);
|
|
return;
|
|
}
|
|
}
|
|
throw new Error('Failed to check post');
|
|
}
|
|
|
|
window.open(post.url, '_blank');
|
|
|
|
await fetchPosts();
|
|
} catch (error) {
|
|
alert('Fehler beim Abhaken des Beitrags');
|
|
console.error('Error checking post:', error);
|
|
}
|
|
}
|
|
|
|
async function promptEditTarget(postId, currentTarget) {
|
|
const defaultValue = Number.isFinite(currentTarget) ? String(currentTarget) : '';
|
|
const newValue = prompt(`Neue Anzahl (1-${MAX_PROFILES}):`, defaultValue);
|
|
|
|
if (newValue === null) {
|
|
return;
|
|
}
|
|
|
|
const parsed = parseInt(newValue, 10);
|
|
if (Number.isNaN(parsed) || parsed < 1 || parsed > MAX_PROFILES) {
|
|
alert(`Bitte gib eine Zahl zwischen 1 und ${MAX_PROFILES} ein.`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/posts/${postId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ target_count: parsed })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json().catch(() => null);
|
|
const message = data && data.error ? data.error : 'Fehler beim Aktualisieren der Anzahl.';
|
|
alert(message);
|
|
return;
|
|
}
|
|
|
|
await fetchPosts();
|
|
} catch (error) {
|
|
alert('Fehler beim Aktualisieren der Anzahl.');
|
|
console.error('Error updating target count:', error);
|
|
}
|
|
}
|
|
|
|
// Delete post
|
|
async function deletePost(postId) {
|
|
if (!confirm('Beitrag wirklich löschen?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/posts/${postId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete post');
|
|
}
|
|
|
|
await fetchPosts();
|
|
} catch (error) {
|
|
alert('Fehler beim Löschen des Beitrags');
|
|
console.error('Error deleting post:', error);
|
|
}
|
|
}
|
|
|
|
// Utility functions
|
|
function showLoading() {
|
|
document.getElementById('loading').style.display = 'block';
|
|
document.getElementById('postsContainer').style.display = 'none';
|
|
}
|
|
|
|
function hideLoading() {
|
|
document.getElementById('loading').style.display = 'none';
|
|
document.getElementById('postsContainer').style.display = 'block';
|
|
}
|
|
|
|
function showError(message) {
|
|
const errorEl = document.getElementById('error');
|
|
errorEl.textContent = message;
|
|
errorEl.style.display = 'block';
|
|
}
|
|
|
|
function hideError() {
|
|
document.getElementById('error').style.display = 'none';
|
|
}
|
|
|
|
function escapeHtml(unsafe) {
|
|
if (unsafe === null || unsafe === undefined) {
|
|
unsafe = '';
|
|
}
|
|
|
|
if (typeof unsafe !== 'string') {
|
|
unsafe = String(unsafe);
|
|
}
|
|
|
|
return unsafe
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
// Auto-check on page load if URL parameter is present
|
|
function checkAutoCheck() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const autoCheckUrl = urlParams.get('check');
|
|
|
|
if (autoCheckUrl) {
|
|
// Try to check this URL automatically
|
|
fetch(`${API_URL}/check-by-url`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
url: decodeURIComponent(autoCheckUrl),
|
|
profile_number: currentProfile
|
|
})
|
|
}).then(() => {
|
|
// Remove the parameter from URL
|
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
fetchPosts();
|
|
}).catch(console.error);
|
|
}
|
|
}
|
|
|
|
if (screenshotModalClose) {
|
|
screenshotModalClose.addEventListener('click', closeScreenshotModal);
|
|
}
|
|
|
|
if (screenshotModalBackdrop) {
|
|
screenshotModalBackdrop.addEventListener('click', closeScreenshotModal);
|
|
}
|
|
|
|
if (screenshotModalImage) {
|
|
screenshotModalImage.addEventListener('click', (event) => {
|
|
event.stopPropagation();
|
|
toggleScreenshotZoom();
|
|
});
|
|
|
|
screenshotModalImage.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
event.preventDefault();
|
|
toggleScreenshotZoom();
|
|
}
|
|
});
|
|
|
|
screenshotModalImage.setAttribute('tabindex', '0');
|
|
screenshotModalImage.setAttribute('role', 'button');
|
|
screenshotModalImage.setAttribute('aria-label', 'Screenshot vergrößern oder verkleinern');
|
|
}
|
|
|
|
document.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Escape') {
|
|
if (screenshotModalZoomed) {
|
|
resetScreenshotZoom();
|
|
return;
|
|
}
|
|
closeScreenshotModal();
|
|
}
|
|
});
|
|
|
|
window.addEventListener('resize', () => {
|
|
if (screenshotModal && screenshotModal.classList.contains('open') && !screenshotModalZoomed) {
|
|
applyScreenshotModalSize();
|
|
}
|
|
});
|
|
|
|
// Initialize
|
|
loadProfile();
|
|
startProfilePolling();
|
|
fetchPosts();
|
|
checkAutoCheck();
|
|
|
|
// Auto-refresh every 30 seconds
|
|
setInterval(fetchPosts, 30000);
|