minor changes
This commit is contained in:
121
web/index.html
121
web/index.html
@@ -480,6 +480,127 @@
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Sports scoring Section -->
|
||||
<section class="settings-section">
|
||||
<h2 class="section-title">🏷️ Sport-Post-Scoring</h2>
|
||||
<p class="section-description">
|
||||
Analysiert Beitragstexte nach Sport-Begriffen (z.B. Fußball, Volleyball) und weist einen Score zu.
|
||||
Beiträge oberhalb des Schwellwerts würden später automatisch ausgeblendet – aktuell wird nur markiert.
|
||||
</p>
|
||||
|
||||
<form id="moderationSettingsForm">
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<input type="checkbox" id="sportsScoringEnabled" class="form-checkbox">
|
||||
<span>Scoring aktivieren</span>
|
||||
</label>
|
||||
<p class="form-help">
|
||||
Nutze das heuristische Punktesystem, um offensichtliche Sport-/Spiel-Posts zu erkennen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sportsScoreThreshold" class="form-label">Schwellwert für Sport-Posts</label>
|
||||
<input type="number" id="sportsScoreThreshold" class="form-input" min="0" max="50" step="0.5" value="5">
|
||||
<p class="form-help">
|
||||
Ab diesem Score kann der Post automatisch versteckt werden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<input type="checkbox" id="sportsAutoHideEnabled" class="form-checkbox">
|
||||
<span>Automatisch verstecken bei Überschreitung</span>
|
||||
</label>
|
||||
<p class="form-help">
|
||||
Wenn aktiviert, blendet die Extension Posts mit Score ≥ Schwellwert automatisch aus (Feed & Suche).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Gewichte (0–10)</label>
|
||||
<div class="grid-weights">
|
||||
<label class="form-field-inline">
|
||||
<span>Ergebnis (1:0)</span>
|
||||
<input type="number" id="sportWeightScoreline" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Score-Emojis (+3️⃣)</span>
|
||||
<input type="number" id="sportWeightScoreEmoji" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Sport-Emojis (⚽️)</span>
|
||||
<input type="number" id="sportWeightSportEmoji" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Verben (gewinnen)</span>
|
||||
<input type="number" id="sportWeightSportVerb" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Nomen (Liga, Tor)</span>
|
||||
<input type="number" id="sportWeightSportNoun" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Hashtags (#auswärtssieg)</span>
|
||||
<input type="number" id="sportWeightHashtag" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Team-Kürzel (FC…)</span>
|
||||
<input type="number" id="sportWeightTeamToken" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Wettbewerbe (Cup)</span>
|
||||
<input type="number" id="sportWeightCompetition" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Ergebnisbezug (Sieg)</span>
|
||||
<input type="number" id="sportWeightCelebration" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Ort (Auswärts)</span>
|
||||
<input type="number" id="sportWeightLocation" class="form-input" min="0" max="10" step="0.5">
|
||||
</label>
|
||||
</div>
|
||||
<p class="form-help">
|
||||
Je höher das Gewicht, desto stärker zahlt der jeweilige Treffer auf den Sport-Score ein.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Stichwort-Listen (kommagetrennt)</label>
|
||||
<div class="grid-weights">
|
||||
<label class="form-field-inline">
|
||||
<span>Nomen</span>
|
||||
<textarea id="sportTermsNouns" class="form-textarea" rows="2" placeholder="auswärtssieg, liga, tor ..."></textarea>
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Verben</span>
|
||||
<textarea id="sportTermsVerbs" class="form-textarea" rows="2" placeholder="gewinnen, punkten ..."></textarea>
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Wettbewerbe</span>
|
||||
<textarea id="sportTermsCompetitions" class="form-textarea" rows="2" placeholder="bundesliga, cup ..."></textarea>
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Ergebnisbezug</span>
|
||||
<textarea id="sportTermsCelebrations" class="form-textarea" rows="2" placeholder="sieg, tabellenführung ..."></textarea>
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Orte</span>
|
||||
<textarea id="sportTermsLocations" class="form-textarea" rows="2" placeholder="auswärts, stadion ..."></textarea>
|
||||
</label>
|
||||
<label class="form-field-inline">
|
||||
<span>Negativliste</span>
|
||||
<textarea id="sportTermsNegatives" class="form-textarea" rows="2" placeholder="rezept, politik ..."></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<p class="form-help">
|
||||
Leere Felder nutzen die Standardliste. Negativliste senkt den Score bei Treffern.
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Hidden posts / purge settings -->
|
||||
<section class="settings-section">
|
||||
<h2 class="section-title">Versteckte Beiträge bereinigen</h2>
|
||||
|
||||
@@ -95,6 +95,20 @@
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.grid-weights {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.form-field-inline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: #1c1e21;
|
||||
}
|
||||
|
||||
.form-help a {
|
||||
color: #1877f2;
|
||||
text-decoration: none;
|
||||
|
||||
220
web/settings.js
220
web/settings.js
@@ -44,6 +44,33 @@ const PROVIDER_INFO = {
|
||||
let credentials = [];
|
||||
let currentSettings = null;
|
||||
let hiddenSettings = { auto_purge_enabled: true, retention_days: 90 };
|
||||
const SPORT_WEIGHT_FIELDS = [
|
||||
{ key: 'scoreline', id: 'sportWeightScoreline' },
|
||||
{ key: 'scoreEmoji', id: 'sportWeightScoreEmoji' },
|
||||
{ key: 'sportEmoji', id: 'sportWeightSportEmoji' },
|
||||
{ key: 'sportVerb', id: 'sportWeightSportVerb' },
|
||||
{ key: 'sportNoun', id: 'sportWeightSportNoun' },
|
||||
{ key: 'hashtag', id: 'sportWeightHashtag' },
|
||||
{ key: 'teamToken', id: 'sportWeightTeamToken' },
|
||||
{ key: 'competition', id: 'sportWeightCompetition' },
|
||||
{ key: 'celebration', id: 'sportWeightCelebration' },
|
||||
{ key: 'location', id: 'sportWeightLocation' }
|
||||
];
|
||||
const SPORT_TERM_FIELDS = [
|
||||
{ key: 'nouns', id: 'sportTermsNouns', placeholder: 'auswärtssieg, liga, tor ...' },
|
||||
{ key: 'verbs', id: 'sportTermsVerbs', placeholder: 'gewinnen, punkten ...' },
|
||||
{ key: 'competitions', id: 'sportTermsCompetitions', placeholder: 'bundesliga, cup ...' },
|
||||
{ key: 'celebrations', id: 'sportTermsCelebrations', placeholder: 'sieg, tabellenführung ...' },
|
||||
{ key: 'locations', id: 'sportTermsLocations', placeholder: 'auswärts, stadion ...' },
|
||||
{ key: 'negatives', id: 'sportTermsNegatives', placeholder: 'rezept, politik ...' }
|
||||
];
|
||||
let moderationSettings = {
|
||||
sports_scoring_enabled: true,
|
||||
sports_score_threshold: 5,
|
||||
sports_score_weights: {},
|
||||
sports_terms: {},
|
||||
sports_auto_hide_enabled: false
|
||||
};
|
||||
|
||||
function apiFetch(url, options = {}) {
|
||||
return fetch(url, {...options, credentials: 'include'});
|
||||
@@ -213,6 +240,153 @@ async function purgeHiddenNow() {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeSportsScoreThresholdInput(value) {
|
||||
const parsed = parseFloat(value);
|
||||
if (Number.isNaN(parsed) || parsed < 0) {
|
||||
return 5;
|
||||
}
|
||||
return Math.min(50, Math.max(0, parsed));
|
||||
}
|
||||
|
||||
function normalizeSportWeightInput(value, fallback = 1) {
|
||||
const parsed = parseFloat(value);
|
||||
if (Number.isNaN(parsed) || parsed < 0) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.min(10, Math.max(0, parsed));
|
||||
}
|
||||
|
||||
function applyWeightInputs(enabled) {
|
||||
SPORT_WEIGHT_FIELDS.forEach(({ id }) => {
|
||||
const input = document.getElementById(id);
|
||||
if (input) {
|
||||
input.disabled = !enabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyTermInputs(enabled) {
|
||||
SPORT_TERM_FIELDS.forEach(({ id }) => {
|
||||
const input = document.getElementById(id);
|
||||
if (input) {
|
||||
input.disabled = !enabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function serializeTermListInput(value) {
|
||||
if (!value || typeof value !== 'string') return [];
|
||||
return value
|
||||
.split(',')
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry) => entry)
|
||||
.slice(0, 200);
|
||||
}
|
||||
|
||||
function renderTermList(list) {
|
||||
if (!Array.isArray(list) || !list.length) return '';
|
||||
return list.join(', ');
|
||||
}
|
||||
|
||||
function applyModerationSettingsUI() {
|
||||
const enabledToggle = document.getElementById('sportsScoringEnabled');
|
||||
const thresholdInput = document.getElementById('sportsScoreThreshold');
|
||||
const autoHideToggle = document.getElementById('sportsAutoHideEnabled');
|
||||
if (enabledToggle) {
|
||||
enabledToggle.checked = !!moderationSettings.sports_scoring_enabled;
|
||||
}
|
||||
if (thresholdInput) {
|
||||
thresholdInput.value = moderationSettings.sports_score_threshold ?? 5;
|
||||
thresholdInput.disabled = !enabledToggle?.checked;
|
||||
}
|
||||
if (autoHideToggle) {
|
||||
autoHideToggle.checked = !!moderationSettings.sports_auto_hide_enabled;
|
||||
autoHideToggle.disabled = !enabledToggle?.checked;
|
||||
}
|
||||
SPORT_WEIGHT_FIELDS.forEach(({ key, id }) => {
|
||||
const input = document.getElementById(id);
|
||||
if (input) {
|
||||
const value = moderationSettings.sports_score_weights?.[key];
|
||||
input.value = typeof value === 'number' ? value : '';
|
||||
input.disabled = !enabledToggle?.checked;
|
||||
}
|
||||
});
|
||||
SPORT_TERM_FIELDS.forEach(({ key, id }) => {
|
||||
const input = document.getElementById(id);
|
||||
if (input) {
|
||||
input.value = renderTermList(moderationSettings.sports_terms?.[key]);
|
||||
input.disabled = !enabledToggle?.checked;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadModerationSettings() {
|
||||
const res = await apiFetch(`${API_URL}/moderation-settings`);
|
||||
if (!res.ok) throw new Error('Konnte Moderations-Einstellungen nicht laden');
|
||||
const data = await res.json();
|
||||
moderationSettings = {
|
||||
sports_scoring_enabled: !!data.sports_scoring_enabled,
|
||||
sports_score_threshold: normalizeSportsScoreThresholdInput(data.sports_score_threshold),
|
||||
sports_score_weights: data.sports_score_weights || {},
|
||||
sports_terms: data.sports_terms || {},
|
||||
sports_auto_hide_enabled: !!data.sports_auto_hide_enabled
|
||||
};
|
||||
applyModerationSettingsUI();
|
||||
}
|
||||
|
||||
async function saveModerationSettings(event, { silent = false } = {}) {
|
||||
if (event && typeof event.preventDefault === 'function') {
|
||||
event.preventDefault();
|
||||
}
|
||||
const enabledToggle = document.getElementById('sportsScoringEnabled');
|
||||
const thresholdInput = document.getElementById('sportsScoreThreshold');
|
||||
const autoHideToggle = document.getElementById('sportsAutoHideEnabled');
|
||||
const enabled = enabledToggle ? enabledToggle.checked : true;
|
||||
const threshold = thresholdInput
|
||||
? normalizeSportsScoreThresholdInput(thresholdInput.value)
|
||||
: moderationSettings.sports_score_threshold;
|
||||
const autoHide = autoHideToggle ? autoHideToggle.checked : false;
|
||||
const weights = {};
|
||||
SPORT_WEIGHT_FIELDS.forEach(({ key, id }) => {
|
||||
const input = document.getElementById(id);
|
||||
weights[key] = normalizeSportWeightInput(input ? input.value : moderationSettings.sports_score_weights?.[key], moderationSettings.sports_score_weights?.[key] ?? 1);
|
||||
});
|
||||
const terms = {};
|
||||
SPORT_TERM_FIELDS.forEach(({ key, id }) => {
|
||||
const input = document.getElementById(id);
|
||||
terms[key] = serializeTermListInput(input ? input.value : moderationSettings.sports_terms?.[key]);
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await apiFetch(`${API_URL}/moderation-settings`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
sports_scoring_enabled: enabled,
|
||||
sports_score_threshold: threshold,
|
||||
sports_auto_hide_enabled: autoHide,
|
||||
sports_score_weights: weights,
|
||||
sports_terms: terms
|
||||
})
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
throw new Error(err.error || 'Fehler beim Speichern');
|
||||
}
|
||||
moderationSettings = await res.json();
|
||||
applyModerationSettingsUI();
|
||||
if (!silent) {
|
||||
showSuccess('✅ Sport-Scoring gespeichert');
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (!silent) {
|
||||
showError('❌ ' + err.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function shorten(text, maxLength = 80) {
|
||||
if (typeof text !== 'string') {
|
||||
return '';
|
||||
@@ -848,6 +1022,7 @@ async function saveAllSettings(event) {
|
||||
const results = await Promise.all([
|
||||
saveSettings(null, { silent: true }),
|
||||
saveHiddenSettings(null, { silent: true }),
|
||||
saveModerationSettings(null, { silent: true }),
|
||||
saveAllFriends({ silent: true })
|
||||
]);
|
||||
|
||||
@@ -978,6 +1153,49 @@ if (autoPurgeHiddenToggle) {
|
||||
});
|
||||
}
|
||||
|
||||
const sportsScoringToggle = document.getElementById('sportsScoringEnabled');
|
||||
const sportsScoreInput = document.getElementById('sportsScoreThreshold');
|
||||
if (sportsScoringToggle && sportsScoreInput) {
|
||||
sportsScoringToggle.addEventListener('change', () => {
|
||||
sportsScoreInput.disabled = !sportsScoringToggle.checked;
|
||||
applyWeightInputs(sportsScoringToggle.checked);
|
||||
applyTermInputs(sportsScoringToggle.checked);
|
||||
const autoHideToggle = document.getElementById('sportsAutoHideEnabled');
|
||||
if (autoHideToggle) {
|
||||
autoHideToggle.disabled = !sportsScoringToggle.checked;
|
||||
}
|
||||
});
|
||||
sportsScoreInput.addEventListener('blur', () => {
|
||||
sportsScoreInput.value = normalizeSportsScoreThresholdInput(sportsScoreInput.value);
|
||||
});
|
||||
SPORT_WEIGHT_FIELDS.forEach(({ id }) => {
|
||||
const input = document.getElementById(id);
|
||||
if (input) {
|
||||
input.addEventListener('blur', () => {
|
||||
input.value = normalizeSportWeightInput(input.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
SPORT_TERM_FIELDS.forEach(({ id }) => {
|
||||
const input = document.getElementById(id);
|
||||
if (input) {
|
||||
input.addEventListener('blur', () => {
|
||||
input.value = renderTermList(serializeTermListInput(input.value));
|
||||
});
|
||||
}
|
||||
});
|
||||
const moderationForm = document.getElementById('moderationSettingsForm');
|
||||
if (moderationForm) {
|
||||
moderationForm.addEventListener('submit', (e) => saveModerationSettings(e));
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
Promise.all([loadCredentials(), loadSettings(), loadHiddenSettings(), loadProfileFriends()]).catch(err => showError(err.message));
|
||||
Promise.all([
|
||||
loadCredentials(),
|
||||
loadSettings(),
|
||||
loadHiddenSettings(),
|
||||
loadModerationSettings(),
|
||||
loadProfileFriends()
|
||||
]).catch(err => showError(err.message));
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user