mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-01 10:22:25 +00:00
Add trusted hide thresholds to moderation settings
This commit is contained in:
1 parent
e13e8d21c6
commit
49a0ccf2b7
9 files changed
+558
-15
No files matched your search
@@ -238,7 +238,7 @@
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-base font-semibold text-text">Safety & Moderation</h3>
|
||||
<p class="text-sm text-muted">
|
||||
Choose how many trusted reports it takes to blur thumbnails or block autoplay previews.
|
||||
Choose how many trusted reports or mutes it takes to blur thumbnails, block autoplay previews, or hide videos entirely.
|
||||
Lower numbers are stricter.
|
||||
</p>
|
||||
</div>
|
||||
@@ -278,6 +278,47 @@
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="grid gap-4 sm:grid-cols-2"
|
||||
data-role="trusted-hide-controls"
|
||||
>
|
||||
<label
|
||||
class="block text-sm text-muted-strong"
|
||||
for="profileModerationMuteHideThreshold"
|
||||
data-role="trusted-hide-control"
|
||||
>
|
||||
<span class="mb-2 block font-medium">Hide after trusted mutes</span>
|
||||
<input
|
||||
id="profileModerationMuteHideThreshold"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
min="0"
|
||||
step="1"
|
||||
class="input"
|
||||
/>
|
||||
<span class="mt-1 block text-xs text-muted">
|
||||
Hide videos when this many trusted contacts add the author to their mute list.
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="block text-sm text-muted-strong"
|
||||
for="profileModerationSpamHideThreshold"
|
||||
data-role="trusted-hide-control"
|
||||
>
|
||||
<span class="mb-2 block font-medium">Hide after trusted spam reports</span>
|
||||
<input
|
||||
id="profileModerationSpamHideThreshold"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
min="0"
|
||||
step="1"
|
||||
class="input"
|
||||
/>
|
||||
<span class="mt-1 block text-xs text-muted">
|
||||
Hide videos entirely once trusted contacts report spam at or above this count.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<button
|
||||
id="profileModerationSave"
|
||||
|
||||
@@ -30,6 +30,8 @@ Thread new moderation behaviors through the same service → stage → app → U
|
||||
## Defaults (policy)
|
||||
- Blur video thumbnails if **≥ 3** F1 friends report `nudity`.
|
||||
- Disable autoplay preview if **≥ 2** F1 friends report `nudity`.
|
||||
- Hide videos when **≥ 1** trusted contact mutes the author (`TRUSTED_MUTE_HIDE_THRESHOLD`).
|
||||
- Hide videos when **≥ 3** trusted contacts file spam reports (`TRUSTED_SPAM_HIDE_THRESHOLD`).
|
||||
- Downrank author when any F1 has them in mute list (10000).
|
||||
- Opt-in admin lists (30000 with `d=bitvid:admin:*`) can hard-hide content.
|
||||
- Trust seeds from `DEFAULT_TRUST_SEED_NPUBS` count as baseline F1 contacts (toggle via `FEATURE_TRUST_SEEDS`).
|
||||
@@ -50,7 +52,13 @@ New operators should also note that fresh viewer accounts automatically inherit
|
||||
|
||||
### Safety & Moderation controls
|
||||
|
||||
The profile modal now exposes the blur and autoplay thresholds so operators can dial in stricter or more permissive behavior. Enter a non-negative whole number to override the default or leave the field blank to fall back to the baseline values above. Adjustments are stored locally, applied immediately to the active feed, and rehydrate on every load.
|
||||
The profile modal now exposes blur, autoplay, and hide thresholds so operators can dial in stricter or more permissive behavior. Enter a non-negative whole number to override the default or leave the field blank to fall back to the baseline values above. Adjustments are stored locally, applied immediately to the active feed, and rehydrate on every load.
|
||||
|
||||
### Runtime flags
|
||||
|
||||
- `TRUSTED_MUTE_HIDE_THRESHOLD` — numeric default for the trusted mute hide control (default `1`).
|
||||
- `TRUSTED_SPAM_HIDE_THRESHOLD` — numeric default for the trusted spam hide control (default `3`).
|
||||
- `FEATURE_TRUSTED_HIDE_CONTROLS` — boolean toggle to hide/show the new trusted hide controls in the UI (default `true`).
|
||||
|
||||
## Files in this folder
|
||||
- `web-of-trust.md` — how we compute trust signals and thresholds
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
- Automated coverage: `tests/moderation/trusted-report-count.test.mjs` (run via `npm run test:unit`).
|
||||
- [ ] Blur at ≥3 F1 reports of `nudity`; autoplay blocked at ≥2.
|
||||
- Automated coverage: `tests/visual/moderation.spec.ts` blur and autoplay scenarios (run via `npm run test:visual`).
|
||||
- [ ] Hide videos once trusted mute/spam thresholds are met.
|
||||
- Manual: adjust thresholds in the profile modal, simulate trusted mutes/reports, and verify cards disappear until "show anyway" is used.
|
||||
- [ ] “Show anyway” works and records only local pref.
|
||||
- Automated coverage: `tests/visual/moderation.spec.ts` show-anyway persistence scenario (run via `npm run test:visual`).
|
||||
- [ ] Mute list (10000) downranks/hides consistently.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
- `blurThumbnail = trustedReportCount(event,'nudity') >= 3`
|
||||
- `hideAutoplay = trustedReportCount(event,'nudity') >= 2`
|
||||
- `downrankIfMutedByF1 = true`
|
||||
- `hideIfTrustedMuteCount(author) >= 1`
|
||||
- `hideIfTrustedSpamReports(event) >= 3`
|
||||
|
||||
### Why these numbers?
|
||||
- F1-only reports resist Sybil attacks.
|
||||
@@ -56,6 +58,14 @@ function shouldBlurThumb(eventId: string, ctx: Ctx): boolean {
|
||||
function shouldHideAutoplay(eventId: string, ctx: Ctx): boolean {
|
||||
return trustedReportCount(eventId, 'nudity', ctx.viewerFollows, ctx.reports) >= 2;
|
||||
}
|
||||
|
||||
function shouldHideForTrustedMute(authorHex: string, ctx: Ctx): boolean {
|
||||
return ctx.trustedMuteCount(authorHex) >= 1;
|
||||
}
|
||||
|
||||
function shouldHideForTrustedSpam(eventId: string, ctx: Ctx): boolean {
|
||||
return trustedReportCount(eventId, 'spam', ctx.viewerFollows, ctx.reports) >= 3;
|
||||
}
|
||||
```
|
||||
|
||||
## UI rules
|
||||
|
||||
@@ -14,7 +14,11 @@ import { accessControl } from "./accessControl.js";
|
||||
import { safeDecodeMagnet } from "./magnetUtils.js";
|
||||
import { extractMagnetHints, normalizeAndAugmentMagnet } from "./magnet.js";
|
||||
import { deriveTorrentPlaybackConfig } from "./playbackUtils.js";
|
||||
import { URL_FIRST_ENABLED } from "./constants.js";
|
||||
import {
|
||||
URL_FIRST_ENABLED,
|
||||
getTrustedMuteHideThreshold,
|
||||
getTrustedSpamHideThreshold,
|
||||
} from "./constants.js";
|
||||
import { attachHealthBadges } from "./gridHealth.js";
|
||||
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
|
||||
import { updateVideoCardSourceVisibility } from "./utils/cardSourceVisibility.js";
|
||||
@@ -4999,6 +5003,16 @@ class Application {
|
||||
const defaultAutoplay = Number.isFinite(defaults?.autoplayBlockThreshold)
|
||||
? Math.max(0, Math.floor(defaults.autoplayBlockThreshold))
|
||||
: 2;
|
||||
const defaultTrustedMuteHide = Number.isFinite(
|
||||
defaults?.trustedMuteHideThreshold,
|
||||
)
|
||||
? Math.max(0, Math.floor(defaults.trustedMuteHideThreshold))
|
||||
: getTrustedMuteHideThreshold();
|
||||
const defaultTrustedSpamHide = Number.isFinite(
|
||||
defaults?.trustedSpamHideThreshold,
|
||||
)
|
||||
? Math.max(0, Math.floor(defaults.trustedSpamHideThreshold))
|
||||
: getTrustedSpamHideThreshold();
|
||||
|
||||
const blurSource = Number.isFinite(settings?.blurThreshold)
|
||||
? Math.max(0, Math.floor(settings.blurThreshold))
|
||||
@@ -5006,10 +5020,18 @@ class Application {
|
||||
const autoplaySource = Number.isFinite(settings?.autoplayBlockThreshold)
|
||||
? Math.max(0, Math.floor(settings.autoplayBlockThreshold))
|
||||
: defaultAutoplay;
|
||||
const muteHideSource = Number.isFinite(settings?.trustedMuteHideThreshold)
|
||||
? Math.max(0, Math.floor(settings.trustedMuteHideThreshold))
|
||||
: defaultTrustedMuteHide;
|
||||
const spamHideSource = Number.isFinite(settings?.trustedSpamHideThreshold)
|
||||
? Math.max(0, Math.floor(settings.trustedSpamHideThreshold))
|
||||
: defaultTrustedSpamHide;
|
||||
|
||||
return {
|
||||
blurThreshold: blurSource,
|
||||
autoplayBlockThreshold: autoplaySource,
|
||||
trustedMuteHideThreshold: muteHideSource,
|
||||
trustedSpamHideThreshold: spamHideSource,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+112
@@ -11,6 +11,9 @@ const DEFAULT_FLAGS = Object.freeze({
|
||||
FEATURE_WATCH_HISTORY_V2: true,
|
||||
FEATURE_PUBLISH_NIP71: false,
|
||||
FEATURE_TRUST_SEEDS: true, // Rollback: disable to drop baseline trust seeds without code changes.
|
||||
FEATURE_TRUSTED_HIDE_CONTROLS: true,
|
||||
TRUSTED_MUTE_HIDE_THRESHOLD: 1,
|
||||
TRUSTED_SPAM_HIDE_THRESHOLD: 3,
|
||||
WSS_TRACKERS: Object.freeze([
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
"wss://tracker.fastcast.nz",
|
||||
@@ -33,6 +36,9 @@ const runtimeFlags = (() => {
|
||||
FEATURE_WATCH_HISTORY_V2: DEFAULT_FLAGS.FEATURE_WATCH_HISTORY_V2,
|
||||
FEATURE_PUBLISH_NIP71: DEFAULT_FLAGS.FEATURE_PUBLISH_NIP71,
|
||||
FEATURE_TRUST_SEEDS: DEFAULT_FLAGS.FEATURE_TRUST_SEEDS,
|
||||
FEATURE_TRUSTED_HIDE_CONTROLS: DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS,
|
||||
TRUSTED_MUTE_HIDE_THRESHOLD: DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
TRUSTED_SPAM_HIDE_THRESHOLD: DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
WSS_TRACKERS: [...DEFAULT_FLAGS.WSS_TRACKERS],
|
||||
};
|
||||
if (globalScope) {
|
||||
@@ -98,6 +104,20 @@ function freezeTrackers(list) {
|
||||
return Object.freeze([...list]);
|
||||
}
|
||||
|
||||
function coerceNonNegativeInteger(value, fallback) {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const sanitized = Math.max(0, Math.floor(numeric));
|
||||
if (!Number.isFinite(sanitized)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
export let URL_FIRST_ENABLED = coerceBoolean(
|
||||
runtimeFlags.URL_FIRST_ENABLED,
|
||||
DEFAULT_FLAGS.URL_FIRST_ENABLED
|
||||
@@ -128,10 +148,31 @@ export let FEATURE_TRUST_SEEDS = coerceBoolean(
|
||||
DEFAULT_FLAGS.FEATURE_TRUST_SEEDS
|
||||
);
|
||||
|
||||
export let FEATURE_TRUSTED_HIDE_CONTROLS = coerceBoolean(
|
||||
runtimeFlags.FEATURE_TRUSTED_HIDE_CONTROLS,
|
||||
DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS
|
||||
);
|
||||
|
||||
export let WSS_TRACKERS = freezeTrackers(
|
||||
sanitizeTrackerList(runtimeFlags.WSS_TRACKERS)
|
||||
);
|
||||
|
||||
export let TRUSTED_MUTE_HIDE_THRESHOLD = coerceNonNegativeInteger(
|
||||
runtimeFlags.TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD
|
||||
);
|
||||
|
||||
export let TRUSTED_SPAM_HIDE_THRESHOLD = coerceNonNegativeInteger(
|
||||
runtimeFlags.TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD
|
||||
);
|
||||
|
||||
export const DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD =
|
||||
DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
|
||||
export const DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD =
|
||||
DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
|
||||
Object.defineProperty(runtimeFlags, "URL_FIRST_ENABLED", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
@@ -210,6 +251,20 @@ Object.defineProperty(runtimeFlags, "FEATURE_TRUST_SEEDS", {
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(runtimeFlags, "FEATURE_TRUSTED_HIDE_CONTROLS", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return FEATURE_TRUSTED_HIDE_CONTROLS;
|
||||
},
|
||||
set(next) {
|
||||
FEATURE_TRUSTED_HIDE_CONTROLS = coerceBoolean(
|
||||
next,
|
||||
DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(runtimeFlags, "WSS_TRACKERS", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
@@ -221,6 +276,34 @@ Object.defineProperty(runtimeFlags, "WSS_TRACKERS", {
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(runtimeFlags, "TRUSTED_MUTE_HIDE_THRESHOLD", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
},
|
||||
set(next) {
|
||||
TRUSTED_MUTE_HIDE_THRESHOLD = coerceNonNegativeInteger(
|
||||
next,
|
||||
DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(runtimeFlags, "TRUSTED_SPAM_HIDE_THRESHOLD", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
},
|
||||
set(next) {
|
||||
TRUSTED_SPAM_HIDE_THRESHOLD = coerceNonNegativeInteger(
|
||||
next,
|
||||
DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Ensure the runtime object reflects the sanitized defaults immediately.
|
||||
runtimeFlags.URL_FIRST_ENABLED = URL_FIRST_ENABLED;
|
||||
runtimeFlags.ACCEPT_LEGACY_V1 = ACCEPT_LEGACY_V1;
|
||||
@@ -228,7 +311,10 @@ runtimeFlags.VIEW_FILTER_INCLUDE_LEGACY_VIDEO = VIEW_FILTER_INCLUDE_LEGACY_VIDEO
|
||||
runtimeFlags.FEATURE_WATCH_HISTORY_V2 = FEATURE_WATCH_HISTORY_V2;
|
||||
runtimeFlags.FEATURE_PUBLISH_NIP71 = FEATURE_PUBLISH_NIP71;
|
||||
runtimeFlags.FEATURE_TRUST_SEEDS = FEATURE_TRUST_SEEDS;
|
||||
runtimeFlags.FEATURE_TRUSTED_HIDE_CONTROLS = FEATURE_TRUSTED_HIDE_CONTROLS;
|
||||
runtimeFlags.WSS_TRACKERS = WSS_TRACKERS;
|
||||
runtimeFlags.TRUSTED_MUTE_HIDE_THRESHOLD = TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
runtimeFlags.TRUSTED_SPAM_HIDE_THRESHOLD = TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
|
||||
export function setUrlFirstEnabled(next) {
|
||||
runtimeFlags.URL_FIRST_ENABLED = next;
|
||||
@@ -259,6 +345,11 @@ export function setTrustSeedsEnabled(next) {
|
||||
return FEATURE_TRUST_SEEDS;
|
||||
}
|
||||
|
||||
export function setTrustedHideControlsEnabled(next) {
|
||||
runtimeFlags.FEATURE_TRUSTED_HIDE_CONTROLS = next;
|
||||
return FEATURE_TRUSTED_HIDE_CONTROLS;
|
||||
}
|
||||
|
||||
export function resetRuntimeFlags() {
|
||||
setUrlFirstEnabled(DEFAULT_FLAGS.URL_FIRST_ENABLED);
|
||||
setAcceptLegacyV1(DEFAULT_FLAGS.ACCEPT_LEGACY_V1);
|
||||
@@ -267,6 +358,9 @@ export function resetRuntimeFlags() {
|
||||
);
|
||||
setWatchHistoryV2Enabled(DEFAULT_FLAGS.FEATURE_WATCH_HISTORY_V2);
|
||||
setTrustSeedsEnabled(DEFAULT_FLAGS.FEATURE_TRUST_SEEDS);
|
||||
setTrustedHideControlsEnabled(DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS);
|
||||
setTrustedMuteHideThreshold(DEFAULT_FLAGS.TRUSTED_MUTE_HIDE_THRESHOLD);
|
||||
setTrustedSpamHideThreshold(DEFAULT_FLAGS.TRUSTED_SPAM_HIDE_THRESHOLD);
|
||||
setWssTrackers(DEFAULT_FLAGS.WSS_TRACKERS);
|
||||
}
|
||||
|
||||
@@ -277,3 +371,21 @@ export function setWatchHistoryV2Enabled(next) {
|
||||
return FEATURE_WATCH_HISTORY_V2;
|
||||
}
|
||||
|
||||
export function setTrustedMuteHideThreshold(next) {
|
||||
runtimeFlags.TRUSTED_MUTE_HIDE_THRESHOLD = next;
|
||||
return TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
}
|
||||
|
||||
export function setTrustedSpamHideThreshold(next) {
|
||||
runtimeFlags.TRUSTED_SPAM_HIDE_THRESHOLD = next;
|
||||
return TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
}
|
||||
|
||||
export function getTrustedMuteHideThreshold() {
|
||||
return TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
}
|
||||
|
||||
export function getTrustedSpamHideThreshold() {
|
||||
return TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
}
|
||||
|
||||
+97
-6
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
getTrustedMuteHideThreshold,
|
||||
getTrustedSpamHideThreshold,
|
||||
} from "../constants.js";
|
||||
import { userLogger } from "../utils/logger.js";
|
||||
import { sanitizeProfileMediaUrl } from "../utils/profileMedia.js";
|
||||
import {
|
||||
@@ -25,13 +31,50 @@ const MODERATION_OVERRIDE_STORAGE_VERSION = 1;
|
||||
const MODERATION_SETTINGS_STORAGE_KEY = "bitvid:moderationSettings:v1";
|
||||
const MODERATION_SETTINGS_STORAGE_VERSION = 1;
|
||||
|
||||
const DEFAULT_MODERATION_SETTINGS = Object.freeze({
|
||||
blurThreshold: 3,
|
||||
autoplayBlockThreshold: 2,
|
||||
});
|
||||
function computeDefaultModerationSettings() {
|
||||
let runtimeMuteHide = DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD;
|
||||
let runtimeSpamHide = DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD;
|
||||
|
||||
try {
|
||||
if (typeof getTrustedMuteHideThreshold === "function") {
|
||||
runtimeMuteHide = getTrustedMuteHideThreshold();
|
||||
}
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[cache.computeDefaultModerationSettings] Failed to read runtime mute hide threshold:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof getTrustedSpamHideThreshold === "function") {
|
||||
runtimeSpamHide = getTrustedSpamHideThreshold();
|
||||
}
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
"[cache.computeDefaultModerationSettings] Failed to read runtime spam hide threshold:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
blurThreshold: 3,
|
||||
autoplayBlockThreshold: 2,
|
||||
trustedMuteHideThreshold: sanitizeModerationThreshold(
|
||||
runtimeMuteHide,
|
||||
DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD,
|
||||
),
|
||||
trustedSpamHideThreshold: sanitizeModerationThreshold(
|
||||
runtimeSpamHide,
|
||||
DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_MODERATION_SETTINGS = Object.freeze(computeDefaultModerationSettings());
|
||||
|
||||
function createDefaultModerationSettings() {
|
||||
return { ...DEFAULT_MODERATION_SETTINGS };
|
||||
return { ...computeDefaultModerationSettings() };
|
||||
}
|
||||
|
||||
function sanitizeModerationThreshold(value, fallback) {
|
||||
@@ -716,7 +759,9 @@ function haveModerationSettingsChanged(previous, next) {
|
||||
|
||||
return (
|
||||
previous.blurThreshold !== next.blurThreshold ||
|
||||
previous.autoplayBlockThreshold !== next.autoplayBlockThreshold
|
||||
previous.autoplayBlockThreshold !== next.autoplayBlockThreshold ||
|
||||
previous.trustedMuteHideThreshold !== next.trustedMuteHideThreshold ||
|
||||
previous.trustedSpamHideThreshold !== next.trustedSpamHideThreshold
|
||||
);
|
||||
}
|
||||
|
||||
@@ -767,6 +812,14 @@ export function loadModerationSettingsFromStorage() {
|
||||
overrides.autoplayBlockThreshold,
|
||||
defaults.autoplayBlockThreshold,
|
||||
),
|
||||
trustedMuteHideThreshold: sanitizeModerationThreshold(
|
||||
overrides.trustedMuteHideThreshold,
|
||||
defaults.trustedMuteHideThreshold,
|
||||
),
|
||||
trustedSpamHideThreshold: sanitizeModerationThreshold(
|
||||
overrides.trustedSpamHideThreshold,
|
||||
defaults.trustedSpamHideThreshold,
|
||||
),
|
||||
};
|
||||
} catch (error) {
|
||||
moderationSettings = createDefaultModerationSettings();
|
||||
@@ -795,6 +848,20 @@ function persistModerationSettingsToStorage() {
|
||||
overrides.autoplayBlockThreshold = moderationSettings.autoplayBlockThreshold;
|
||||
}
|
||||
|
||||
if (
|
||||
moderationSettings.trustedMuteHideThreshold !==
|
||||
defaults.trustedMuteHideThreshold
|
||||
) {
|
||||
overrides.trustedMuteHideThreshold = moderationSettings.trustedMuteHideThreshold;
|
||||
}
|
||||
|
||||
if (
|
||||
moderationSettings.trustedSpamHideThreshold !==
|
||||
defaults.trustedSpamHideThreshold
|
||||
) {
|
||||
overrides.trustedSpamHideThreshold = moderationSettings.trustedSpamHideThreshold;
|
||||
}
|
||||
|
||||
if (Object.keys(overrides).length === 0) {
|
||||
try {
|
||||
localStorage.removeItem(MODERATION_SETTINGS_STORAGE_KEY);
|
||||
@@ -855,6 +922,30 @@ export function setModerationSettings(partial = {}, { persist = true } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(partial, "trustedMuteHideThreshold")) {
|
||||
const value = partial.trustedMuteHideThreshold;
|
||||
const sanitized =
|
||||
value === null
|
||||
? defaults.trustedMuteHideThreshold
|
||||
: sanitizeModerationThreshold(value, defaults.trustedMuteHideThreshold);
|
||||
if (next.trustedMuteHideThreshold !== sanitized) {
|
||||
next.trustedMuteHideThreshold = sanitized;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(partial, "trustedSpamHideThreshold")) {
|
||||
const value = partial.trustedSpamHideThreshold;
|
||||
const sanitized =
|
||||
value === null
|
||||
? defaults.trustedSpamHideThreshold
|
||||
: sanitizeModerationThreshold(value, defaults.trustedSpamHideThreshold);
|
||||
if (next.trustedSpamHideThreshold !== sanitized) {
|
||||
next.trustedSpamHideThreshold = sanitized;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
if (persist) {
|
||||
persistModerationSettingsToStorage();
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
MAX_WALLET_DEFAULT_ZAP as CONFIG_MAX_WALLET_DEFAULT_ZAP,
|
||||
} from "../config.js";
|
||||
import { normalizeDesignSystemContext } from "../designSystem.js";
|
||||
import {
|
||||
getTrustedMuteHideThreshold,
|
||||
getTrustedSpamHideThreshold,
|
||||
RUNTIME_FLAGS,
|
||||
} from "../constants.js";
|
||||
import { devLogger, userLogger } from "../utils/logger.js";
|
||||
|
||||
const noop = () => {};
|
||||
@@ -30,6 +35,8 @@ function createInternalDefaultNwcSettings() {
|
||||
const DEFAULT_INTERNAL_MODERATION_SETTINGS = Object.freeze({
|
||||
blurThreshold: 3,
|
||||
autoplayBlockThreshold: 2,
|
||||
trustedMuteHideThreshold: getTrustedMuteHideThreshold(),
|
||||
trustedSpamHideThreshold: getTrustedSpamHideThreshold(),
|
||||
});
|
||||
|
||||
function createInternalDefaultModerationSettings() {
|
||||
@@ -834,9 +841,13 @@ export class ProfileModalController {
|
||||
this.moderationSettingsCard = null;
|
||||
this.moderationBlurInput = null;
|
||||
this.moderationAutoplayInput = null;
|
||||
this.moderationMuteHideInput = null;
|
||||
this.moderationSpamHideInput = null;
|
||||
this.moderationSaveButton = null;
|
||||
this.moderationResetButton = null;
|
||||
this.moderationStatusText = null;
|
||||
this.moderationHideControlsGroup = null;
|
||||
this.moderationHideControlElements = [];
|
||||
this.moderatorSection = null;
|
||||
this.moderatorEmpty = null;
|
||||
this.adminModeratorList = null;
|
||||
@@ -1019,12 +1030,25 @@ export class ProfileModalController {
|
||||
document.getElementById("profileModerationBlurThreshold") || null;
|
||||
this.moderationAutoplayInput =
|
||||
document.getElementById("profileModerationAutoplayThreshold") || null;
|
||||
this.moderationMuteHideInput =
|
||||
document.getElementById("profileModerationMuteHideThreshold") || null;
|
||||
this.moderationSpamHideInput =
|
||||
document.getElementById("profileModerationSpamHideThreshold") || null;
|
||||
this.moderationSaveButton =
|
||||
document.getElementById("profileModerationSave") || null;
|
||||
this.moderationResetButton =
|
||||
document.getElementById("profileModerationReset") || null;
|
||||
this.moderationStatusText =
|
||||
document.getElementById("profileModerationStatus") || null;
|
||||
this.moderationHideControlsGroup =
|
||||
this.moderationSettingsCard?.querySelector(
|
||||
"[data-role=\"trusted-hide-controls\"]",
|
||||
) || null;
|
||||
this.moderationHideControlElements = Array.from(
|
||||
this.moderationSettingsCard?.querySelectorAll(
|
||||
"[data-role=\"trusted-hide-control\"]",
|
||||
) || [],
|
||||
);
|
||||
|
||||
this.moderatorSection =
|
||||
document.getElementById("adminModeratorsSection") || null;
|
||||
@@ -1254,6 +1278,18 @@ export class ProfileModalController {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.moderationMuteHideInput instanceof HTMLElement) {
|
||||
this.moderationMuteHideInput.addEventListener("input", () => {
|
||||
this.applyModerationSettingsControlState();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.moderationSpamHideInput instanceof HTMLElement) {
|
||||
this.moderationSpamHideInput.addEventListener("input", () => {
|
||||
this.applyModerationSettingsControlState();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.moderationSaveButton instanceof HTMLElement) {
|
||||
this.moderationSaveButton.addEventListener("click", () => {
|
||||
void this.handleModerationSettingsSave();
|
||||
@@ -3364,6 +3400,24 @@ export class ProfileModalController {
|
||||
),
|
||||
),
|
||||
),
|
||||
trustedMuteHideThreshold: Math.max(
|
||||
0,
|
||||
Math.floor(
|
||||
Number(
|
||||
defaults.trustedMuteHideThreshold ??
|
||||
DEFAULT_INTERNAL_MODERATION_SETTINGS.trustedMuteHideThreshold,
|
||||
),
|
||||
),
|
||||
),
|
||||
trustedSpamHideThreshold: Math.max(
|
||||
0,
|
||||
Math.floor(
|
||||
Number(
|
||||
defaults.trustedSpamHideThreshold ??
|
||||
DEFAULT_INTERNAL_MODERATION_SETTINGS.trustedSpamHideThreshold,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
return sanitized;
|
||||
@@ -3377,10 +3431,18 @@ export class ProfileModalController {
|
||||
const autoplay = Number.isFinite(settings?.autoplayBlockThreshold)
|
||||
? Math.max(0, Math.floor(settings.autoplayBlockThreshold))
|
||||
: defaults.autoplayBlockThreshold;
|
||||
const muteHide = Number.isFinite(settings?.trustedMuteHideThreshold)
|
||||
? Math.max(0, Math.floor(settings.trustedMuteHideThreshold))
|
||||
: defaults.trustedMuteHideThreshold;
|
||||
const spamHide = Number.isFinite(settings?.trustedSpamHideThreshold)
|
||||
? Math.max(0, Math.floor(settings.trustedSpamHideThreshold))
|
||||
: defaults.trustedSpamHideThreshold;
|
||||
|
||||
return {
|
||||
blurThreshold: blur,
|
||||
autoplayBlockThreshold: autoplay,
|
||||
trustedMuteHideThreshold: muteHide,
|
||||
trustedSpamHideThreshold: spamHide,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3411,15 +3473,27 @@ export class ProfileModalController {
|
||||
this.moderationAutoplayInput,
|
||||
defaults.autoplayBlockThreshold,
|
||||
);
|
||||
const muteHide = parse(
|
||||
this.moderationMuteHideInput,
|
||||
defaults.trustedMuteHideThreshold,
|
||||
);
|
||||
const spamHide = parse(
|
||||
this.moderationSpamHideInput,
|
||||
defaults.trustedSpamHideThreshold,
|
||||
);
|
||||
|
||||
const valid = blur.valid && autoplay.valid;
|
||||
const valid = blur.valid && autoplay.valid && muteHide.valid && spamHide.valid;
|
||||
const values = {
|
||||
blurThreshold: blur.value,
|
||||
autoplayBlockThreshold: autoplay.value,
|
||||
trustedMuteHideThreshold: muteHide.value,
|
||||
trustedSpamHideThreshold: spamHide.value,
|
||||
};
|
||||
const overrides = {
|
||||
blurThreshold: blur.override,
|
||||
autoplayBlockThreshold: autoplay.override,
|
||||
trustedMuteHideThreshold: muteHide.override,
|
||||
trustedSpamHideThreshold: spamHide.override,
|
||||
};
|
||||
|
||||
return { defaults, values, overrides, valid };
|
||||
@@ -3434,7 +3508,11 @@ export class ProfileModalController {
|
||||
const isDirty =
|
||||
result.valid &&
|
||||
(baseline.blurThreshold !== result.values.blurThreshold ||
|
||||
baseline.autoplayBlockThreshold !== result.values.autoplayBlockThreshold);
|
||||
baseline.autoplayBlockThreshold !== result.values.autoplayBlockThreshold ||
|
||||
baseline.trustedMuteHideThreshold !==
|
||||
result.values.trustedMuteHideThreshold ||
|
||||
baseline.trustedSpamHideThreshold !==
|
||||
result.values.trustedSpamHideThreshold);
|
||||
button.disabled = !(result.valid && isDirty);
|
||||
if (button.disabled) {
|
||||
button.setAttribute("aria-disabled", "true");
|
||||
@@ -3450,6 +3528,51 @@ export class ProfileModalController {
|
||||
return result;
|
||||
}
|
||||
|
||||
areTrustedHideControlsEnabled() {
|
||||
if (
|
||||
RUNTIME_FLAGS &&
|
||||
typeof RUNTIME_FLAGS === "object" &&
|
||||
RUNTIME_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
updateTrustedHideControlsVisibility() {
|
||||
const shouldShow = this.areTrustedHideControlsEnabled();
|
||||
const targets = new Set();
|
||||
|
||||
if (this.moderationHideControlsGroup instanceof HTMLElement) {
|
||||
targets.add(this.moderationHideControlsGroup);
|
||||
}
|
||||
|
||||
if (Array.isArray(this.moderationHideControlElements)) {
|
||||
for (const element of this.moderationHideControlElements) {
|
||||
if (element instanceof HTMLElement) {
|
||||
targets.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targets.forEach((element) => {
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldShow) {
|
||||
element.classList.remove("hidden");
|
||||
element.removeAttribute("hidden");
|
||||
element.removeAttribute("aria-hidden");
|
||||
} else {
|
||||
element.classList.add("hidden");
|
||||
element.setAttribute("hidden", "");
|
||||
element.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateModerationSettingsStatus(message = "", variant = "info") {
|
||||
if (!(this.moderationStatusText instanceof HTMLElement)) {
|
||||
return;
|
||||
@@ -3470,6 +3593,7 @@ export class ProfileModalController {
|
||||
if (!service) {
|
||||
this.moderationSettingsDefaults = createInternalDefaultModerationSettings();
|
||||
this.currentModerationSettings = createInternalDefaultModerationSettings();
|
||||
this.updateTrustedHideControlsVisibility();
|
||||
this.applyModerationSettingsControlState({ resetStatus: true });
|
||||
return;
|
||||
}
|
||||
@@ -3498,6 +3622,20 @@ export class ProfileModalController {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.moderationMuteHideInput instanceof HTMLInputElement) {
|
||||
this.moderationMuteHideInput.value = String(
|
||||
normalized.trustedMuteHideThreshold,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.moderationSpamHideInput instanceof HTMLInputElement) {
|
||||
this.moderationSpamHideInput.value = String(
|
||||
normalized.trustedSpamHideThreshold,
|
||||
);
|
||||
}
|
||||
|
||||
this.updateTrustedHideControlsVisibility();
|
||||
|
||||
this.applyModerationSettingsControlState({ resetStatus: true });
|
||||
}
|
||||
|
||||
@@ -3538,6 +3676,26 @@ export class ProfileModalController {
|
||||
payload.autoplayBlockThreshold = inputState.overrides.autoplayBlockThreshold;
|
||||
}
|
||||
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
inputState.overrides,
|
||||
"trustedMuteHideThreshold",
|
||||
)
|
||||
) {
|
||||
payload.trustedMuteHideThreshold =
|
||||
inputState.overrides.trustedMuteHideThreshold;
|
||||
}
|
||||
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
inputState.overrides,
|
||||
"trustedSpamHideThreshold",
|
||||
)
|
||||
) {
|
||||
payload.trustedSpamHideThreshold =
|
||||
inputState.overrides.trustedSpamHideThreshold;
|
||||
}
|
||||
|
||||
try {
|
||||
const updated =
|
||||
typeof service.updateModerationSettings === "function"
|
||||
@@ -3554,6 +3712,16 @@ export class ProfileModalController {
|
||||
normalized.autoplayBlockThreshold,
|
||||
);
|
||||
}
|
||||
if (this.moderationMuteHideInput instanceof HTMLInputElement) {
|
||||
this.moderationMuteHideInput.value = String(
|
||||
normalized.trustedMuteHideThreshold,
|
||||
);
|
||||
}
|
||||
if (this.moderationSpamHideInput instanceof HTMLInputElement) {
|
||||
this.moderationSpamHideInput.value = String(
|
||||
normalized.trustedSpamHideThreshold,
|
||||
);
|
||||
}
|
||||
this.applyModerationSettingsControlState();
|
||||
this.updateModerationSettingsStatus("Moderation settings saved.", "success");
|
||||
this.showSuccess("Moderation settings saved.");
|
||||
@@ -3609,6 +3777,17 @@ export class ProfileModalController {
|
||||
normalized.autoplayBlockThreshold,
|
||||
);
|
||||
}
|
||||
if (this.moderationMuteHideInput instanceof HTMLInputElement) {
|
||||
this.moderationMuteHideInput.value = String(
|
||||
normalized.trustedMuteHideThreshold,
|
||||
);
|
||||
}
|
||||
if (this.moderationSpamHideInput instanceof HTMLInputElement) {
|
||||
this.moderationSpamHideInput.value = String(
|
||||
normalized.trustedSpamHideThreshold,
|
||||
);
|
||||
}
|
||||
this.updateTrustedHideControlsVisibility();
|
||||
this.applyModerationSettingsControlState({ resetStatus: true });
|
||||
this.updateModerationSettingsStatus(
|
||||
"Moderation defaults restored.",
|
||||
|
||||
@@ -22,6 +22,10 @@ const TEMPLATE_HTML = `
|
||||
<div id="profileModerationSettings">
|
||||
<input id="profileModerationBlurThreshold" type="number" />
|
||||
<input id="profileModerationAutoplayThreshold" type="number" />
|
||||
<div data-role="trusted-hide-controls">
|
||||
<input id="profileModerationMuteHideThreshold" type="number" />
|
||||
<input id="profileModerationSpamHideThreshold" type="number" />
|
||||
</div>
|
||||
<button id="profileModerationSave" type="button">Save</button>
|
||||
<button id="profileModerationReset" type="button">Reset</button>
|
||||
<p id="profileModerationStatus"></p>
|
||||
@@ -62,10 +66,20 @@ beforeEach(async () => {
|
||||
};
|
||||
};
|
||||
|
||||
storedSettings = { blurThreshold: 3, autoplayBlockThreshold: 2 };
|
||||
storedSettings = {
|
||||
blurThreshold: 3,
|
||||
autoplayBlockThreshold: 2,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
};
|
||||
moderationService = {
|
||||
getDefaultModerationSettings() {
|
||||
return { blurThreshold: 3, autoplayBlockThreshold: 2 };
|
||||
return {
|
||||
blurThreshold: 3,
|
||||
autoplayBlockThreshold: 2,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
};
|
||||
},
|
||||
getActiveModerationSettings() {
|
||||
return { ...storedSettings };
|
||||
@@ -84,10 +98,33 @@ beforeEach(async () => {
|
||||
const value = partial.autoplayBlockThreshold;
|
||||
storedSettings.autoplayBlockThreshold = value === null ? 2 : value;
|
||||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
partial,
|
||||
"trustedMuteHideThreshold",
|
||||
)
|
||||
) {
|
||||
const value = partial.trustedMuteHideThreshold;
|
||||
storedSettings.trustedMuteHideThreshold = value === null ? 1 : value;
|
||||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
partial,
|
||||
"trustedSpamHideThreshold",
|
||||
)
|
||||
) {
|
||||
const value = partial.trustedSpamHideThreshold;
|
||||
storedSettings.trustedSpamHideThreshold = value === null ? 3 : value;
|
||||
}
|
||||
return { ...storedSettings };
|
||||
},
|
||||
resetModerationSettings() {
|
||||
storedSettings = { blurThreshold: 3, autoplayBlockThreshold: 2 };
|
||||
storedSettings = {
|
||||
blurThreshold: 3,
|
||||
autoplayBlockThreshold: 2,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
};
|
||||
return { ...storedSettings };
|
||||
},
|
||||
};
|
||||
@@ -150,6 +187,8 @@ test("moderation settings save updates service and disables control", async () =
|
||||
|
||||
assert.equal(controller.moderationBlurInput.value, "3");
|
||||
assert.equal(controller.moderationAutoplayInput.value, "2");
|
||||
assert.equal(controller.moderationMuteHideInput.value, "1");
|
||||
assert.equal(controller.moderationSpamHideInput.value, "3");
|
||||
assert.equal(controller.moderationSaveButton.disabled, true);
|
||||
|
||||
controller.moderationBlurInput.value = "5";
|
||||
@@ -160,6 +199,14 @@ test("moderation settings save updates service and disables control", async () =
|
||||
controller.moderationAutoplayInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
controller.moderationMuteHideInput.value = "2";
|
||||
controller.moderationMuteHideInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
controller.moderationSpamHideInput.value = "6";
|
||||
controller.moderationSpamHideInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
|
||||
assert.equal(controller.moderationSaveButton.disabled, false);
|
||||
|
||||
@@ -168,6 +215,8 @@ test("moderation settings save updates service and disables control", async () =
|
||||
assert.deepEqual(storedSettings, {
|
||||
blurThreshold: 5,
|
||||
autoplayBlockThreshold: 4,
|
||||
trustedMuteHideThreshold: 2,
|
||||
trustedSpamHideThreshold: 6,
|
||||
});
|
||||
assert.equal(controller.moderationSaveButton.disabled, true);
|
||||
assert.equal(
|
||||
@@ -178,24 +227,37 @@ test("moderation settings save updates service and disables control", async () =
|
||||
assert.deepEqual(callbackPayloads[0].settings, {
|
||||
blurThreshold: 5,
|
||||
autoplayBlockThreshold: 4,
|
||||
trustedMuteHideThreshold: 2,
|
||||
trustedSpamHideThreshold: 6,
|
||||
});
|
||||
});
|
||||
|
||||
test("moderation reset restores defaults and clearing inputs uses defaults", async () => {
|
||||
storedSettings = { blurThreshold: 6, autoplayBlockThreshold: 4 };
|
||||
storedSettings = {
|
||||
blurThreshold: 6,
|
||||
autoplayBlockThreshold: 4,
|
||||
trustedMuteHideThreshold: 3,
|
||||
trustedSpamHideThreshold: 5,
|
||||
};
|
||||
await controller.load();
|
||||
|
||||
assert.equal(controller.moderationBlurInput.value, "6");
|
||||
assert.equal(controller.moderationAutoplayInput.value, "4");
|
||||
assert.equal(controller.moderationMuteHideInput.value, "3");
|
||||
assert.equal(controller.moderationSpamHideInput.value, "5");
|
||||
|
||||
const resetContext = await controller.handleModerationSettingsReset();
|
||||
assert.equal(resetContext.success, true);
|
||||
assert.deepEqual(storedSettings, {
|
||||
blurThreshold: 3,
|
||||
autoplayBlockThreshold: 2,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
});
|
||||
assert.equal(controller.moderationBlurInput.value, "3");
|
||||
assert.equal(controller.moderationAutoplayInput.value, "2");
|
||||
assert.equal(controller.moderationMuteHideInput.value, "1");
|
||||
assert.equal(controller.moderationSpamHideInput.value, "3");
|
||||
assert.equal(
|
||||
controller.moderationStatusText.textContent,
|
||||
"Moderation defaults restored.",
|
||||
@@ -206,21 +268,35 @@ test("moderation reset restores defaults and clearing inputs uses defaults", asy
|
||||
controller.moderationBlurInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
controller.moderationSpamHideInput.value = "8";
|
||||
controller.moderationSpamHideInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
const saveResult = await controller.handleModerationSettingsSave();
|
||||
assert.equal(saveResult.success, true);
|
||||
assert.deepEqual(storedSettings, {
|
||||
blurThreshold: 7,
|
||||
autoplayBlockThreshold: 2,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 8,
|
||||
});
|
||||
|
||||
controller.moderationBlurInput.value = "";
|
||||
controller.moderationAutoplayInput.value = "";
|
||||
controller.moderationMuteHideInput.value = "";
|
||||
controller.moderationSpamHideInput.value = "";
|
||||
controller.moderationBlurInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
controller.moderationAutoplayInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
controller.moderationMuteHideInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
controller.moderationSpamHideInput.dispatchEvent(
|
||||
new windowRef.Event("input", { bubbles: true }),
|
||||
);
|
||||
assert.equal(controller.moderationSaveButton.disabled, false);
|
||||
|
||||
const revertContext = await controller.handleModerationSettingsSave();
|
||||
@@ -228,5 +304,7 @@ test("moderation reset restores defaults and clearing inputs uses defaults", asy
|
||||
assert.deepEqual(storedSettings, {
|
||||
blurThreshold: 3,
|
||||
autoplayBlockThreshold: 2,
|
||||
trustedMuteHideThreshold: 1,
|
||||
trustedSpamHideThreshold: 3,
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user