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

@@ -4,7 +4,7 @@ import { showModelModal } from './ModelModal.js';
import { toggleShowcase } from './showcase/ShowcaseView.js';
import { bulkManager } from '../../managers/BulkManager.js';
import { modalManager } from '../../managers/ModalManager.js';
import { NSFW_LEVELS, getBaseModelAbbreviation, getSubTypeAbbreviation, MODEL_SUBTYPE_DISPLAY_NAMES } from '../../utils/constants.js';
import { NSFW_LEVELS, getBaseModelAbbreviation, getSubTypeAbbreviation, getMatureBlurThreshold, MODEL_SUBTYPE_DISPLAY_NAMES } from '../../utils/constants.js';
import { MODEL_TYPES } from '../../api/apiConfig.js';
import { getModelApiClient } from '../../api/modelApiFactory.js';
import { showDeleteModal } from '../../utils/modalUtils.js';
@@ -478,7 +478,8 @@ export function createModelCard(model, modelType) {
card.dataset.nsfwLevel = nsfwLevel;
// Determine if the preview should be blurred based on NSFW level and user settings
const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
const matureBlurThreshold = getMatureBlurThreshold(state.settings);
const shouldBlur = state.settings.blur_mature_content && nsfwLevel >= matureBlurThreshold;
if (shouldBlur) {
card.classList.add('nsfw-content');
}

View File

@@ -6,7 +6,7 @@
import { showToast, copyToClipboard, getNSFWLevelName } from '../../../utils/uiHelpers.js';
import { state } from '../../../state/index.js';
import { getModelApiClient } from '../../../api/modelApiFactory.js';
import { NSFW_LEVELS } from '../../../utils/constants.js';
import { NSFW_LEVELS, getMatureBlurThreshold } from '../../../utils/constants.js';
import { getNsfwLevelSelector } from '../NsfwLevelSelector.js';
/**
@@ -607,7 +607,8 @@ function applyNsfwLevelChange(mediaWrapper, nsfwLevel) {
}
mediaWrapper.dataset.nsfwLevel = String(nsfwLevel);
const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
const matureBlurThreshold = getMatureBlurThreshold(state.settings);
const shouldBlur = state.settings.blur_mature_content && nsfwLevel >= matureBlurThreshold;
let overlay = mediaWrapper.querySelector('.nsfw-overlay');
let toggleBtn = mediaWrapper.querySelector('.toggle-blur-btn');

View File

@@ -6,7 +6,7 @@ import { showToast } from '../../../utils/uiHelpers.js';
import { state } from '../../../state/index.js';
import { modalManager } from '../../../managers/ModalManager.js';
import { translate } from '../../../utils/i18nHelpers.js';
import { NSFW_LEVELS } from '../../../utils/constants.js';
import { NSFW_LEVELS, getMatureBlurThreshold } from '../../../utils/constants.js';
import {
initLazyLoading,
initNsfwBlurHandlers,
@@ -184,7 +184,8 @@ function renderMediaItem(img, index, exampleFiles) {
// Check if media should be blurred
const nsfwLevel = img.nsfwLevel !== undefined ? img.nsfwLevel : 0;
const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
const matureBlurThreshold = getMatureBlurThreshold(state.settings);
const shouldBlur = state.settings.blur_mature_content && nsfwLevel >= matureBlurThreshold;
// Determine NSFW warning text based on level
let nsfwText = "Mature Content";