chore: checkpoint current working state
This commit is contained in:
106
web/app.js
106
web/app.js
@@ -16,7 +16,7 @@ let focusHandled = false;
|
||||
let initialTabOverride = null;
|
||||
let focusTabAdjusted = null;
|
||||
|
||||
let currentProfile = 1;
|
||||
let currentProfile = null;
|
||||
let currentTab = 'pending';
|
||||
let posts = [];
|
||||
let includeExpiredPosts = false;
|
||||
@@ -39,6 +39,10 @@ const PROFILE_NAMES = {
|
||||
5: 'Profil 5'
|
||||
};
|
||||
|
||||
function isValidProfileNumber(value) {
|
||||
return Number.isInteger(value) && value >= 1 && value <= MAX_PROFILES;
|
||||
}
|
||||
|
||||
function redirectToLogin() {
|
||||
try {
|
||||
const redirect = encodeURIComponent(window.location.href);
|
||||
@@ -408,12 +412,18 @@ function persistIncludeExpiredPreference(value) {
|
||||
}
|
||||
|
||||
function getPendingOpenCooldownStorageKey(profileNumber = currentProfile) {
|
||||
const safeProfile = profileNumber || currentProfile || 1;
|
||||
const safeProfile = isValidProfileNumber(profileNumber) ? profileNumber : null;
|
||||
if (!safeProfile) {
|
||||
return null;
|
||||
}
|
||||
return `${PENDING_OPEN_COOLDOWN_STORAGE_KEY}:${safeProfile}`;
|
||||
}
|
||||
|
||||
function loadPendingOpenCooldownMap(profileNumber = currentProfile) {
|
||||
const storageKey = getPendingOpenCooldownStorageKey(profileNumber);
|
||||
if (!storageKey) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const raw = localStorage.getItem(storageKey);
|
||||
if (raw) {
|
||||
@@ -441,6 +451,9 @@ function loadPendingOpenCooldownMap(profileNumber = currentProfile) {
|
||||
|
||||
function persistPendingOpenCooldownMap(profileNumber, map) {
|
||||
const storageKey = getPendingOpenCooldownStorageKey(profileNumber);
|
||||
if (!storageKey) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify(map || {}));
|
||||
} catch (error) {
|
||||
@@ -3209,6 +3222,10 @@ function applyAutoRefreshSettings() {
|
||||
autoRefreshTimer = null;
|
||||
}
|
||||
|
||||
if (!isValidProfileNumber(currentProfile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoRefreshIntervalSelect) {
|
||||
const disabled = !autoRefreshSettings.enabled || updatesStreamHealthy;
|
||||
autoRefreshIntervalSelect.disabled = disabled;
|
||||
@@ -3229,6 +3246,9 @@ function applyAutoRefreshSettings() {
|
||||
if (document.hidden) {
|
||||
return;
|
||||
}
|
||||
if (!isValidProfileNumber(currentProfile)) {
|
||||
return;
|
||||
}
|
||||
fetchPosts({ showLoader: false });
|
||||
}, autoRefreshSettings.interval);
|
||||
}
|
||||
@@ -3506,8 +3526,17 @@ async function pushProfileState(profileNumber) {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureProfileSelected() {
|
||||
if (isValidProfileNumber(currentProfile)) {
|
||||
hideError();
|
||||
return true;
|
||||
}
|
||||
showError('Bitte zuerst ein Profil auswählen.');
|
||||
return false;
|
||||
}
|
||||
|
||||
function applyProfileNumber(profileNumber, { fromBackend = false } = {}) {
|
||||
if (!profileNumber) {
|
||||
if (!isValidProfileNumber(profileNumber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3524,6 +3553,7 @@ function applyProfileNumber(profileNumber, { fromBackend = false } = {}) {
|
||||
|
||||
currentProfile = profileNumber;
|
||||
localStorage.setItem('profileNumber', currentProfile);
|
||||
hideError();
|
||||
|
||||
if (!fromBackend) {
|
||||
pushProfileState(currentProfile);
|
||||
@@ -3533,20 +3563,32 @@ function applyProfileNumber(profileNumber, { fromBackend = false } = {}) {
|
||||
pendingOpenCooldownMap = loadPendingOpenCooldownMap(currentProfile);
|
||||
renderPosts();
|
||||
maybeAutoOpenPending('profile');
|
||||
|
||||
if (!posts.length && !isFetchingPosts) {
|
||||
fetchPosts({ showLoader: false });
|
||||
}
|
||||
|
||||
checkAutoCheck();
|
||||
applyAutoRefreshSettings();
|
||||
}
|
||||
|
||||
// Load profile from localStorage
|
||||
function loadProfile() {
|
||||
fetchProfileState().then((backendProfile) => {
|
||||
if (backendProfile) {
|
||||
if (isValidProfileNumber(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 });
|
||||
const parsed = saved ? parseInt(saved, 10) : NaN;
|
||||
if (isValidProfileNumber(parsed)) {
|
||||
applyProfileNumber(parsed, { fromBackend: true });
|
||||
return;
|
||||
}
|
||||
if (profileSelectElement) {
|
||||
profileSelectElement.value = '';
|
||||
}
|
||||
currentProfile = null;
|
||||
showError('Bitte zuerst ein Profil auswählen.');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3572,7 +3614,12 @@ function startProfilePolling() {
|
||||
// Profile selector change handler
|
||||
if (profileSelectElement) {
|
||||
profileSelectElement.addEventListener('change', (e) => {
|
||||
saveProfile(parseInt(e.target.value, 10));
|
||||
const parsed = parseInt(e.target.value, 10);
|
||||
if (!isValidProfileNumber(parsed)) {
|
||||
showError('Bitte zuerst ein Profil auswählen.');
|
||||
return;
|
||||
}
|
||||
saveProfile(parsed);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3762,6 +3809,10 @@ async function fetchPosts({ showLoader = true } = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensureProfileSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isFetchingPosts = true;
|
||||
cancelPendingAutoOpen(false);
|
||||
|
||||
@@ -4038,6 +4089,13 @@ async function mergeSelectedPosts() {
|
||||
// Render posts
|
||||
function renderPosts() {
|
||||
hideLoading();
|
||||
if (!ensureProfileSelected()) {
|
||||
const container = document.getElementById('postsContainer');
|
||||
if (container) {
|
||||
container.innerHTML = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
hideError();
|
||||
|
||||
const container = document.getElementById('postsContainer');
|
||||
@@ -4596,6 +4654,10 @@ function createPostCard(post, status, meta = {}) {
|
||||
|
||||
// Open post and auto-check
|
||||
async function openPost(postId) {
|
||||
if (!ensureProfileSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const post = posts.find((item) => item.id === postId);
|
||||
if (!post) {
|
||||
alert('Beitrag konnte nicht gefunden werden.');
|
||||
@@ -4619,20 +4681,8 @@ async function openPost(postId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!status.canCurrentProfileCheck) {
|
||||
if (status.waitingForNames.length) {
|
||||
const proceed = confirm(`Vorherige Profile müssen zuerst bestätigen (${status.waitingForNames.join(', ')}). Trotzdem bestätigen?`);
|
||||
if (!proceed) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
alert('Der Beitrag kann aktuell nicht abgehakt werden.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const ignoreOrder = !status.canCurrentProfileCheck && status.waitingForNames.length > 0;
|
||||
const ignoreOrder = true;
|
||||
const response = await apiFetch(`${API_URL}/check-by-url`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -4686,6 +4736,9 @@ async function toggleSuccessStatus(postId, isSuccessful) {
|
||||
}
|
||||
|
||||
async function toggleProfileStatus(postId, profileNumber, currentStatus) {
|
||||
if (!ensureProfileSelected()) {
|
||||
return;
|
||||
}
|
||||
if (!profileNumber) {
|
||||
return;
|
||||
}
|
||||
@@ -4698,7 +4751,8 @@ async function toggleProfileStatus(postId, profileNumber, currentStatus) {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
profile_number: profileNumber,
|
||||
status: desiredStatus
|
||||
status: desiredStatus,
|
||||
ignore_order: true
|
||||
})
|
||||
});
|
||||
|
||||
@@ -5058,13 +5112,17 @@ function checkAutoCheck() {
|
||||
const autoCheckUrl = urlParams.get('check');
|
||||
|
||||
if (autoCheckUrl) {
|
||||
if (!ensureProfileSelected()) {
|
||||
return;
|
||||
}
|
||||
// Try to check this URL automatically
|
||||
apiFetch(`${API_URL}/check-by-url`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: decodeURIComponent(autoCheckUrl),
|
||||
profile_number: currentProfile
|
||||
profile_number: currentProfile,
|
||||
ignore_order: true
|
||||
})
|
||||
}).then(() => {
|
||||
// Remove nur den check-Parameter aus der URL
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
<div class="control-group">
|
||||
<label for="profileSelect">Dein Profil:</label>
|
||||
<select id="profileSelect" class="control-select">
|
||||
<option value="">-- Profil wählen --</option>
|
||||
<option value="1">Profil 1</option>
|
||||
<option value="2">Profil 2</option>
|
||||
<option value="3">Profil 3</option>
|
||||
@@ -1089,7 +1090,7 @@
|
||||
<textarea id="aiPromptPrefix" class="form-textarea" rows="4"
|
||||
placeholder="Anweisungen für die KI vor dem Post-Text..."></textarea>
|
||||
<p class="form-help">
|
||||
Dieser Text wird vor dem eigentlichen Post-Text an die KI gesendet. Verwende <code>{FREUNDE}</code> als Platzhalter für Freundesnamen.
|
||||
Dieser Text wird vor dem eigentlichen Post-Text an die KI gesendet. Platzhalter: <code>{FREUNDE}</code> (Freundesnamen), <code>{DATUM}</code> (heutiges Datum), <code>{Profil-1?"Text1":"Text2"}</code> bzw. <code>{Profil-1?"Text1"}</code> für profilabhängige Varianten (auch mit <code>Profile</code>) und <code>{ZUFALL-1-5}</code> für eine Zufallszahl im Bereich.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user