feat: add configurable mature blur threshold setting

Add new setting 'mature_blur_level' with options PG13/R/X/XXX to control
which NSFW rating level triggers blur filtering when NSFW blur is enabled.

- Backend: update preview selection logic to respect threshold
- Frontend: update UI components to use configurable threshold
- Settings: add validation and normalization for mature_blur_level
- Tests: add coverage for new threshold behavior
- Translations: add keys for all supported languages

Fixes #867
This commit is contained in:
Will Miao
2026-03-26 18:24:47 +08:00
parent 3b001a6cd8
commit ceeab0c998
28 changed files with 320 additions and 59 deletions

View File

@@ -10,6 +10,8 @@ import { validatePriorityTagString, getPriorityTagSuggestionsMap, invalidatePrio
import { bannerService } from './BannerService.js';
import { sidebarManager } from '../components/SidebarManager.js';
const VALID_MATURE_BLUR_LEVELS = new Set(['PG13', 'R', 'X', 'XXX']);
export class SettingsManager {
constructor() {
this.initialized = false;
@@ -137,11 +139,25 @@ export class SettingsManager {
backendSettings?.metadata_refresh_skip_paths ?? defaults.metadata_refresh_skip_paths
);
merged.mature_blur_level = this.normalizeMatureBlurLevel(
backendSettings?.mature_blur_level ?? defaults.mature_blur_level
);
Object.keys(merged).forEach(key => this.backendSettingKeys.add(key));
return merged;
}
normalizeMatureBlurLevel(value) {
if (typeof value === 'string') {
const normalized = value.trim().toUpperCase();
if (VALID_MATURE_BLUR_LEVELS.has(normalized)) {
return normalized;
}
}
return 'R';
}
normalizePatternList(value) {
if (Array.isArray(value)) {
const sanitized = value
@@ -682,6 +698,13 @@ export class SettingsManager {
showOnlySFWCheckbox.checked = state.global.settings.show_only_sfw ?? false;
}
const matureBlurLevelSelect = document.getElementById('matureBlurLevel');
if (matureBlurLevelSelect) {
matureBlurLevelSelect.value = this.normalizeMatureBlurLevel(
state.global.settings.mature_blur_level
);
}
const usePortableCheckbox = document.getElementById('usePortableSettings');
if (usePortableCheckbox) {
usePortableCheckbox.checked = !!state.global.settings.use_portable_settings;
@@ -1811,7 +1834,9 @@ export class SettingsManager {
const element = document.getElementById(elementId);
if (!element) return;
const value = element.value;
const value = settingKey === 'mature_blur_level'
? this.normalizeMatureBlurLevel(element.value)
: element.value;
try {
// Update frontend state with mapped keys
@@ -1834,7 +1859,12 @@ export class SettingsManager {
showToast('toast.settings.settingsUpdated', { setting: settingKey.replace(/_/g, ' ') }, 'success');
if (settingKey === 'model_name_display' || settingKey === 'model_card_footer_action' || settingKey === 'update_flag_strategy') {
if (
settingKey === 'model_name_display'
|| settingKey === 'model_card_footer_action'
|| settingKey === 'update_flag_strategy'
|| settingKey === 'mature_blur_level'
) {
this.reloadContent();
}
} catch (error) {