enhance(frontend): センシティブなメディアを開く際に確認ダイアログを出せるように (#14115)
* enhance(frontend): センシティブなメディアを開く際に確認ダイアログを出せるように * Update Changelog
This commit is contained in:
parent
54d0a46378
commit
1f24a8cb5a
|
@ -30,6 +30,7 @@
|
||||||
(Cherry-picked from https://github.com/taiyme/misskey/pull/238)
|
(Cherry-picked from https://github.com/taiyme/misskey/pull/238)
|
||||||
- Enhance: AiScriptを0.19.0にアップデート
|
- Enhance: AiScriptを0.19.0にアップデート
|
||||||
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
|
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
|
||||||
|
- Enhance: センシティブなメディアを開く際に確認ダイアログを出せるように
|
||||||
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
||||||
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
||||||
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
||||||
|
|
|
@ -5008,6 +5008,14 @@ export interface Locale extends ILocale {
|
||||||
* もう一度お試しください。
|
* もう一度お試しください。
|
||||||
*/
|
*/
|
||||||
"tryAgain": string;
|
"tryAgain": string;
|
||||||
|
/**
|
||||||
|
* センシティブなメディアを表示するとき確認する
|
||||||
|
*/
|
||||||
|
"confirmWhenRevealingSensitiveMedia": string;
|
||||||
|
/**
|
||||||
|
* センシティブなメディアです。表示しますか?
|
||||||
|
*/
|
||||||
|
"sensitiveMediaRevealConfirm": string;
|
||||||
"_delivery": {
|
"_delivery": {
|
||||||
/**
|
/**
|
||||||
* 配信状態
|
* 配信状態
|
||||||
|
|
|
@ -1248,6 +1248,8 @@ noDescription: "説明文はありません"
|
||||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||||
inquiry: "お問い合わせ"
|
inquiry: "お問い合わせ"
|
||||||
tryAgain: "もう一度お試しください。"
|
tryAgain: "もう一度お試しください。"
|
||||||
|
confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示するとき確認する"
|
||||||
|
sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?"
|
||||||
|
|
||||||
_delivery:
|
_delivery:
|
||||||
status: "配信状態"
|
status: "配信状態"
|
||||||
|
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@contextmenu.stop
|
@contextmenu.stop
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
>
|
>
|
||||||
<button v-if="hide" :class="$style.hidden" @click="hide = false">
|
<button v-if="hide" :class="$style.hidden" @click="show">
|
||||||
<div :class="$style.hiddenTextWrapper">
|
<div :class="$style.hiddenTextWrapper">
|
||||||
<b v-if="audio.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
|
<b v-if="audio.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
|
||||||
<b v-else style="display: block;"><i class="ti ti-music"></i> {{ defaultStore.state.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
|
<b v-else style="display: block;"><i class="ti ti-music"></i> {{ defaultStore.state.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
|
||||||
|
@ -156,6 +156,18 @@ const audioEl = shallowRef<HTMLAudioElement>();
|
||||||
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
|
||||||
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore'));
|
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore'));
|
||||||
|
|
||||||
|
async function show() {
|
||||||
|
if (props.audio.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.ts.sensitiveMediaRevealConfirm,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Menu
|
// Menu
|
||||||
const menuShowing = ref(false);
|
const menuShowing = ref(false);
|
||||||
|
|
||||||
|
|
|
@ -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="hide = false">
|
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="show">
|
||||||
<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>
|
||||||
|
@ -24,24 +24,30 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { shallowRef, watch, 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 { defaultStore } from '@/store.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
import MkMediaAudio from '@/components/MkMediaAudio.vue';
|
import MkMediaAudio from '@/components/MkMediaAudio.vue';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = defineProps<{
|
||||||
media: Misskey.entities.DriveFile;
|
media: Misskey.entities.DriveFile;
|
||||||
}>(), {
|
}>();
|
||||||
});
|
|
||||||
|
|
||||||
const audioEl = shallowRef<HTMLAudioElement>();
|
|
||||||
const hide = ref(true);
|
const hide = ref(true);
|
||||||
|
|
||||||
watch(audioEl, () => {
|
async function show() {
|
||||||
if (audioEl.value) {
|
if (props.media.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
|
||||||
audioEl.value.volume = 0.3;
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.ts.sensitiveMediaRevealConfirm,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
hide.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -83,11 +83,21 @@ const url = computed(() => (props.raw || defaultStore.state.loadRawImages)
|
||||||
: props.image.thumbnailUrl,
|
: props.image.thumbnailUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
function onclick() {
|
async function onclick(ev: MouseEvent) {
|
||||||
if (!props.controls) {
|
if (!props.controls) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hide.value) {
|
if (hide.value) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (props.image.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.ts.sensitiveMediaRevealConfirm,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
}
|
||||||
|
|
||||||
hide.value = false;
|
hide.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,15 +138,13 @@ onMounted(() => {
|
||||||
pswpModule: PhotoSwipe,
|
pswpModule: PhotoSwipe,
|
||||||
});
|
});
|
||||||
|
|
||||||
lightbox.on('itemData', (ev) => {
|
lightbox.addFilter('itemData', (itemData) => {
|
||||||
const { itemData } = ev;
|
|
||||||
|
|
||||||
// element is children
|
// element is children
|
||||||
const { element } = itemData;
|
const { element } = itemData;
|
||||||
|
|
||||||
const id = element?.dataset.id;
|
const id = element?.dataset.id;
|
||||||
const file = props.mediaList.find(media => media.id === id);
|
const file = props.mediaList.find(media => media.id === id);
|
||||||
if (!file) return;
|
if (!file) return itemData;
|
||||||
|
|
||||||
itemData.src = file.url;
|
itemData.src = file.url;
|
||||||
itemData.w = Number(file.properties.width);
|
itemData.w = Number(file.properties.width);
|
||||||
|
@ -158,6 +156,8 @@ onMounted(() => {
|
||||||
itemData.alt = file.comment ?? file.name;
|
itemData.alt = file.comment ?? file.name;
|
||||||
itemData.comment = file.comment ?? file.name;
|
itemData.comment = file.comment ?? file.name;
|
||||||
itemData.thumbCropped = true;
|
itemData.thumbCropped = true;
|
||||||
|
|
||||||
|
return itemData;
|
||||||
});
|
});
|
||||||
|
|
||||||
lightbox.on('uiRegister', () => {
|
lightbox.on('uiRegister', () => {
|
||||||
|
|
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@contextmenu.stop
|
@contextmenu.stop
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
>
|
>
|
||||||
<button v-if="hide" :class="$style.hidden" @click="hide = false">
|
<button v-if="hide" :class="$style.hidden" @click="show">
|
||||||
<div :class="$style.hiddenTextWrapper">
|
<div :class="$style.hiddenTextWrapper">
|
||||||
<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
|
<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
|
||||||
<b v-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
|
<b v-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
|
||||||
|
@ -176,6 +176,18 @@ 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((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
|
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
|
||||||
|
|
||||||
|
async function show() {
|
||||||
|
if (props.video.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.ts.sensitiveMediaRevealConfirm,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Menu
|
// Menu
|
||||||
const menuShowing = ref(false);
|
const menuShowing = ref(false);
|
||||||
|
|
||||||
|
|
|
@ -169,6 +169,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
|
<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
|
||||||
<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
|
<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
|
||||||
<MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
|
<MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
<MkSelect v-model="serverDisconnectedBehavior">
|
<MkSelect v-model="serverDisconnectedBehavior">
|
||||||
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
||||||
|
@ -315,6 +316,7 @@ const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enabl
|
||||||
const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
|
const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
|
||||||
const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer'));
|
const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer'));
|
||||||
const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
|
const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
|
||||||
|
const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
|
||||||
|
|
||||||
watch(lang, () => {
|
watch(lang, () => {
|
||||||
miLocalStorage.setItem('lang', lang.value as string);
|
miLocalStorage.setItem('lang', lang.value as string);
|
||||||
|
@ -357,6 +359,7 @@ watch([
|
||||||
disableStreamingTimeline,
|
disableStreamingTimeline,
|
||||||
enableSeasonalScreenEffect,
|
enableSeasonalScreenEffect,
|
||||||
alwaysConfirmFollow,
|
alwaysConfirmFollow,
|
||||||
|
confirmWhenRevealingSensitiveMedia,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk();
|
||||||
});
|
});
|
||||||
|
|
|
@ -454,6 +454,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
confirmWhenRevealingSensitiveMedia: {
|
||||||
|
where: 'device',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
sound_masterVolume: {
|
sound_masterVolume: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
|
|
Loading…
Reference in New Issue