fix(frontend): ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正 (#17019)

* fix(frontend): ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正

* Update Changelog

* refactor

* Update Changelog
This commit is contained in:
かっこかり 2026-01-02 21:41:32 +09:00 committed by GitHub
parent a1ba403f9a
commit 9c22538454
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 73 additions and 40 deletions

View File

@ -15,6 +15,7 @@
- Fix: ドライブクリーナーでファイルを削除しても画面に反映されない問題を修正 #16061 - Fix: ドライブクリーナーでファイルを削除しても画面に反映されない問題を修正 #16061
- Fix: 非ログイン時にログインを求めるダイアログが表示された後にダイアログのぼかしが解除されず操作不能になることがある問題を修正 - Fix: 非ログイン時にログインを求めるダイアログが表示された後にダイアログのぼかしが解除されず操作不能になることがある問題を修正
- Fix: ドライブのソートが「登録日(昇順)」の場合に正しく動作しない問題を修正 - Fix: ドライブのソートが「登録日(昇順)」の場合に正しく動作しない問題を修正
- Fix: ファイルタブのセンシティブメディアを開く際に確認ダイアログを出す設定が適用されない問題を修正
### Server ### Server
- Enhance: OAuthのクライアント情報取得Client Information Discoveryにおいて、IndieWeb Living Standard 11 July 2024で定義されているJSONドキュメント形式に対応しました - Enhance: OAuthのクライアント情報取得Client Information Discoveryにおいて、IndieWeb Living Standard 11 July 2024で定義されているJSONドキュメント形式に対応しました

View File

@ -100,6 +100,7 @@ import { hms } from '@/filters/hms.js';
import MkMediaRange from '@/components/MkMediaRange.vue'; import MkMediaRange from '@/components/MkMediaRange.vue';
import { $i, iAmModerator } from '@/i.js'; import { $i, iAmModerator } from '@/i.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { canRevealFile, shouldHideFileByDefault } from '@/utility/sensitive-file.js';
const props = defineProps<{ const props = defineProps<{
audio: Misskey.entities.DriveFile; audio: Misskey.entities.DriveFile;
@ -154,16 +155,11 @@ function hasFocus() {
const playerEl = useTemplateRef('playerEl'); const playerEl = useTemplateRef('playerEl');
const audioEl = useTemplateRef('audioEl'); const audioEl = useTemplateRef('audioEl');
// eslint-disable-next-line vue/no-setup-props-reactivity-loss const hide = ref(shouldHideFileByDefault(props.audio));
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));
async function reveal() { async function reveal() {
if (props.audio.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) { if (!(await canRevealFile(props.audio))) {
const { canceled } = await os.confirm({ return;
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
});
if (canceled) return;
} }
hide.value = false; hide.value = false;

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="$style.root"> <div :class="$style.root">
<MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/> <MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="reveal"> <div v-else-if="hide" :class="$style.sensitive" @click="reveal">
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span> <span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
<b>{{ i18n.ts.sensitive }}</b> <b>{{ i18n.ts.sensitive }}</b>
<span>{{ i18n.ts.clickToShow }}</span> <span>{{ i18n.ts.clickToShow }}</span>
@ -27,23 +27,18 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue'; import { ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import MkMediaAudio from '@/components/MkMediaAudio.vue'; import MkMediaAudio from '@/components/MkMediaAudio.vue';
import { prefer } from '@/preferences.js'; import { shouldHideFileByDefault, canRevealFile } from '@/utility/sensitive-file.js';
const props = defineProps<{ const props = defineProps<{
media: Misskey.entities.DriveFile; media: Misskey.entities.DriveFile;
}>(); }>();
const hide = ref(true); const hide = ref(shouldHideFileByDefault(props.media));
async function reveal() { async function reveal() {
if (props.media.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) { if (!(await canRevealFile(props.media))) {
const { canceled } = await os.confirm({ return;
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
});
if (canceled) return;
} }
hide.value = false; hide.value = false;

View File

@ -77,6 +77,7 @@ import { i18n } from '@/i18n.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { $i, iAmModerator } from '@/i.js'; import { $i, iAmModerator } from '@/i.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { shouldHideFileByDefault, canRevealFile } from '@/utility/sensitive-file.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
image: Misskey.entities.DriveFile; image: Misskey.entities.DriveFile;
@ -106,12 +107,8 @@ async function reveal(ev: MouseEvent) {
if (hide.value) { if (hide.value) {
ev.stopPropagation(); ev.stopPropagation();
if (props.image.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) { if (!(await canRevealFile(props.image))) {
const { canceled } = await os.confirm({ return;
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
});
if (canceled) return;
} }
hide.value = false; hide.value = false;
@ -119,8 +116,8 @@ async function reveal(ev: MouseEvent) {
} }
// Plugin:register_note_view_interruptor 使watch // Plugin:register_note_view_interruptor 使watch
watch(() => props.image, () => { watch(() => props.image, (newImage) => {
hide.value = (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.image.isSensitive && prefer.s.nsfw !== 'ignore'); hide.value = shouldHideFileByDefault(newImage);
}, { }, {
deep: true, deep: true,
immediate: true, immediate: true,

View File

@ -124,6 +124,7 @@ import hasAudio from '@/utility/media-has-audio.js';
import MkMediaRange from '@/components/MkMediaRange.vue'; import MkMediaRange from '@/components/MkMediaRange.vue';
import { $i, iAmModerator } from '@/i.js'; import { $i, iAmModerator } from '@/i.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { shouldHideFileByDefault, canRevealFile } from '@/utility/sensitive-file.js';
const props = defineProps<{ const props = defineProps<{
video: Misskey.entities.DriveFile; video: Misskey.entities.DriveFile;
@ -176,15 +177,11 @@ function hasFocus() {
} }
// eslint-disable-next-line vue/no-setup-props-reactivity-loss // eslint-disable-next-line vue/no-setup-props-reactivity-loss
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.video.isSensitive && prefer.s.nsfw !== 'ignore')); const hide = ref(shouldHideFileByDefault(props.video));
async function reveal() { async function reveal() {
if (props.video.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) { if (!(await canRevealFile(props.video))) {
const { canceled } = await os.confirm({ return;
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
});
if (canceled) return;
} }
hide.value = false; hide.value = false;

View File

@ -6,14 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<template v-for="file in note.files"> <template v-for="file in note.files">
<div <div
v-if="((( v-if="isHiding(file)"
(prefer.s.nsfw === 'force' || file.isSensitive) &&
prefer.s.nsfw !== 'ignore'
) || (prefer.s.dataSaver.media && file.type.startsWith('image/'))) &&
!showingFiles.has(file.id)
)"
:class="[$style.filePreview, { [$style.square]: square }]" :class="[$style.filePreview, { [$style.square]: square }]"
@click="showingFiles.add(file.id)" @click="reveal(file)"
> >
<MkDriveFileThumbnail <MkDriveFileThumbnail
:file="file" :file="file"
@ -49,6 +44,7 @@ import * as Misskey from 'misskey-js';
import { notePage } from '@/filters/note.js'; import { notePage } from '@/filters/note.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { shouldHideFileByDefault, canRevealFile } from '@/utility/sensitive-file.js';
import bytes from '@/filters/bytes.js'; import bytes from '@/filters/bytes.js';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
@ -59,6 +55,24 @@ defineProps<{
}>(); }>();
const showingFiles = ref<Set<string>>(new Set()); const showingFiles = ref<Set<string>>(new Set());
function isHiding(file: Misskey.entities.DriveFile) {
if (shouldHideFileByDefault(file) && !showingFiles.value.has(file.id)) {
if (!file.isSensitive && !file.type.startsWith('image/')) {
return false;
}
return true;
}
return false;
}
async function reveal(file: Misskey.entities.DriveFile) {
if (!(await canRevealFile(file))) {
return;
}
showingFiles.value.add(file.id);
}
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { prefer } from '@/preferences.js';
import { i18n } from '@/i18n.js';
export function shouldHideFileByDefault(file: Misskey.entities.DriveFile): boolean {
if (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) {
return true;
}
if (file.isSensitive && prefer.s.nsfw !== 'ignore') {
return true;
}
return false;
}
export async function canRevealFile(file: Misskey.entities.DriveFile): Promise<boolean> {
if (file.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
const { canceled } = await os.confirm({
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
});
if (canceled) return false;
}
return true;
}