Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ユーザーTLファイル付きノートTLの修正 #580

Merged
merged 13 commits into from
Dec 21, 2024
Merged
7 changes: 7 additions & 0 deletions CHANGELOG_YOJO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ Misskey 2024.10.1

### Client
- Fix: すべてのノートに翻訳ボタンが表示される/本文がなくて投票だけあると翻訳が表示されなくなる [#579](https://github.com/yojo-art/cherrypick/pull/579)
- Fix: ユーザーTLファイル付きノートTLの修正 [#580](https://github.com/yojo-art/cherrypick/pull/580)
- ユーザー設定アピアランスのセンシティブ画像を常に表示が無視されてたのを修正
- ユーザー設定アピアランスの画像を常に非表示が無視されてたのを修正
- ユーザー設定アピアランスの非表示の画像をダブルクリックして開く設定が無視されてたのを修正
- ユーザー設定全般のデータセーバー、メディアの読み込みを無効化が無視されてたのを修正
- センシティブ画像を開く時に年齢確認ダイアログを表示する機能が無視されてたのを修正
- 画像左上にALT/GIF/APNG/センシティブの表示を追加

### Server

Expand Down
110 changes: 98 additions & 12 deletions packages/frontend/src/pages/user/index.timeline.files.files.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,58 @@ SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<div v-if="files.files.length > 0" :class="$style.root">
<div v-if="files.files[0].isSensitive && !showingFiles.includes(files.files[0].id)" :key="files.id + files.files[0].id" :class="$style.img" @click="showingFiles.push(files.files[0].id)">
<div v-if="note.files.length > 0" :class="$style.root">
<div v-if="!showingFiles.includes(note.files[0].id)" :key="note.id + note.files[0].id" :class="$style.img" @click="onClick($event,note.files[0])" @dblclick="onDblClick(note.files[0])">
<!-- TODO: 画像以外のファイルに対応 -->
<ImgWithBlurhash :class="$style.sensitiveImg" :hash="files.files[0].blurhash" :src="thumbnail(files.files[0])" :title="files.files[0].name" :forceBlurhash="true"/>
<ImgWithBlurhash :class="$style.sensitiveImg" :hash="note.files[0].blurhash" :src="thumbnail(note.files[0])" :title="note.files[0].name" :forceBlurhash="true"/>
<div :class="$style.sensitive">
<div>
<div><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</div>
<div v-if="note.files[0].isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.image}${note.files[0].size ? ' ' + bytes(note.files[0].size) : ''})` : '' }}</div>
<div v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.dataSaver.media && note.files[0].size ? bytes(note.files[0].size) : i18n.ts.image }}</div>
<div>{{ i18n.ts.clickToShow }}</div>
</div>
</div>
</div>
<MkA v-else :class="[$style.img, { [$style.multipleImg]: files.files.length > 1 }]" :to="notePage(files)">
<MkA v-else :class="[$style.img, { [$style.multipleImg]: note.files.length > 1 }]" :to="notePage(note)">
<!-- TODO: 画像以外のファイルに対応 -->
<ImgWithBlurhash
:hash="files.files[0].blurhash"
:src="thumbnail(files.files[0])"
:title="files.files[0].name"
:hash="note.files[0].blurhash"
:src="thumbnail(note.files[0])"
:title="note.files[0].name"
@mouseover="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
@mouseout="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
@touchstart="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = true : ''"
@touchend="defaultStore.state.showingAnimatedImages === 'interaction' ? playAnimation = false : ''"
/>
<div :class="$style.indicators">
<div v-if="['image/gif'].includes(note.files[0].type)" :class="$style.indicator">GIF</div>
<div v-if="['image/apng'].includes(note.files[0].type)" :class="$style.indicator">APNG</div>
<div v-if="note.files[0].isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
</div>
</MkA>
<div v-if="files.files.length > 1" :class="$style.multiple">
<div v-if="note.files.length > 1" :class="$style.multiple">
<i class="ti ti-box-multiple"></i>
</div>
</div>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue';
import { onMounted, onUnmounted, ref, watch } from 'vue';
import * as Misskey from 'cherrypick-js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import { notePage } from '@/filters/note.js';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
import MkA from '@/components/global/MkA.vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import * as os from '@/os.js';
import { confirmR18, wasConfirmR18 } from '@/scripts/check-r18.js';
import bytes from '@/filters/bytes.js';

const props = defineProps<{
user: Misskey.entities.UserDetailed;
files: Misskey.entities.DriveFile[];
note: Misskey.entities.Note & { files:Misskey.entities.DriveFile[] };
}>();

const showingFiles = ref<string[]>([]);
Expand All @@ -60,6 +70,56 @@ function thumbnail(image: Misskey.entities.DriveFile): string | null {
: image.thumbnailUrl;
}

async function onClick(ev: MouseEvent, image:Misskey.entities.DriveFile) {
if (!showingFiles.value.includes(image.id)) {
ev.stopPropagation();
if (image.isSensitive && !await confirmR18()) return;
if (image.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
const { canceled } = await os.confirm({
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
});
if (canceled) return;
showingFiles.value.push(image.id);
}
}

if (defaultStore.state.nsfwOpenBehavior === 'doubleClick') os.popup(MkRippleEffect, { x: ev.clientX, y: ev.clientY }, {});
if (defaultStore.state.nsfwOpenBehavior === 'click') showingFiles.value.push(image.id);
}

async function onDblClick(image:Misskey.entities.DriveFile) {
if (image.isSensitive && !await confirmR18()) return;
if (!showingFiles.value.includes(image.id) && defaultStore.state.nsfwOpenBehavior === 'doubleClick') showingFiles.value.push(image.id);
}

watch(() => props.note, () => {
if (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) {
//hide = true;
} else {
for (const image of props.note.files) {
if (image.isSensitive) {
if (defaultStore.state.nsfw !== 'ignore') {
//hide = true;
penginn-net marked this conversation as resolved.
Show resolved Hide resolved
} else {
if (wasConfirmR18()) {
if (!showingFiles.value.includes(image.id)) {
showingFiles.value.push(image.id);
}
}
}
} else {
if (!showingFiles.value.includes(image.id)) {
showingFiles.value.push(image.id);
}
}
}
}
}, {
deep: true,
immediate: true,
});

function resetTimer() {
playAnimation.value = true;
clearTimeout(playAnimationTimer);
Expand Down Expand Up @@ -118,11 +178,16 @@ onUnmounted(() => {
width: 100%;
height: 100%;
display: grid;
place-items: center;
place-items: center;
font-size: 0.8em;
color: #fff;
cursor: pointer;
}
.sensitive > div{
display: flex;
flex-direction: column;
align-items: center;
}

.multipleImg {
filter: brightness(0.9);
Expand All @@ -137,6 +202,27 @@ onUnmounted(() => {
opacity: .9;
}

.indicators {
display: inline-flex;
position: absolute;
top: 10px;
left: 10px;
pointer-events: none;
opacity: .5;
gap: 6px;
}

.indicator {
/* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */
background-color: black;
border-radius: 6px;
color: var(--MI_THEME-accentLighten);
display: inline-block;
font-weight: bold;
font-size: 0.8em;
padding: 2px 5px;
}

@container (max-width: 785px) {
.img {
height: 192px;
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/pages/user/index.timeline.files.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only

<template #default="{ items: user }">
<div :class="$style.stream">
<XFiles v-for="item in user" :key="item.user.id" :user="item.user" :files="item"/>
<XFiles v-for="item in user" :key="item.user.id" :user="item.user" :note="item"/>
</div>
</template>
</MkPagination>
Expand Down
Loading