Merge pull request #12526 from misskey-dev/misskey

This commit is contained in:
NoriDev 2023-12-01 16:51:16 +09:00
commit 2a01eeab9c
20 changed files with 190 additions and 28 deletions

View file

@ -45,6 +45,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGE
- Revert: 사용자 통계 표시 기능 제거 ([MisskeyIO/misskey@114c7fe6](https://github.com/MisskeyIO/misskey/commit/114c7fe6b37dd6bddbcd9d92406f8b13bf688e8b)) - Revert: 사용자 통계 표시 기능 제거 ([MisskeyIO/misskey@114c7fe6](https://github.com/MisskeyIO/misskey/commit/114c7fe6b37dd6bddbcd9d92406f8b13bf688e8b))
### Client ### Client
- Feat: 데이터 절약 모드로 코드 하이라이트 로드를 줄일 수 있음 (misskey-dev/misskey#12526)
- Enhance: 사운드 설정을 기본값으로 복원하거나 저장할 때 확실하게 표시함 - Enhance: 사운드 설정을 기본값으로 복원하거나 저장할 때 확실하게 표시함
- Enhance: 리모트 서버와 동일한 이모지가 존재하지 않는 경우 '이모지 복사'를 비활성화함 - Enhance: 리모트 서버와 동일한 이모지가 존재하지 않는 경우 '이모지 복사'를 비활성화함
- Enhance: 아이콘 장식을 바로 업로드 하거나 드라이브에서 불러올 수 있음 ([Secineralyr/misskey.dream@e358212d](https://github.com/Secineralyr/misskey.dream/commit/e358212da93256749e31d9e0ca9dd2ed37fd548e), [Secineralyr/misskey.dream@52592fea](https://github.com/Secineralyr/misskey.dream/commit/52592fea52684497ba7e07f173aac2b1083afcb1)) - Enhance: 아이콘 장식을 바로 업로드 하거나 드라이브에서 불러올 수 있음 ([Secineralyr/misskey.dream@e358212d](https://github.com/Secineralyr/misskey.dream/commit/e358212da93256749e31d9e0ca9dd2ed37fd548e), [Secineralyr/misskey.dream@52592fea](https://github.com/Secineralyr/misskey.dream/commit/52592fea52684497ba7e07f173aac2b1083afcb1))
@ -52,6 +53,8 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGE
- Enhance: 진동 개선 - Enhance: 진동 개선
- 진동을 사용할 수 없는 환경에서 스위치를 조작할 수 없도록 비활성화 - 진동을 사용할 수 없는 환경에서 스위치를 조작할 수 없도록 비활성화
- 진동을 사용할 수 없는 이유를 보다 명확하게 표시하도록 개선 - 진동을 사용할 수 없는 이유를 보다 명확하게 표시하도록 개선
- Enhance: 데이터 절약 모드 적용 범위를 개별적으로 설정할 수 있음 (misskey-dev/misskey#12526)
- 기존 데이터 절약 모드 설정이 재설정됩니다.
- Fix: '모달 배경색 제거' 옵션이 이모지 피커에 반영되지 않음 - Fix: '모달 배경색 제거' 옵션이 이모지 피커에 반영되지 않음
- Fix: 열람 주의로 설정된 노트의 반응이 더 보기를 눌러야 표시됨 - Fix: 열람 주의로 설정된 노트의 반응이 더 보기를 눌러야 표시됨

View file

@ -1268,6 +1268,8 @@ useGroupedNotifications: "Display grouped notifications"
signupPendingError: "There was a problem verifying the email address. The link may have expired." signupPendingError: "There was a problem verifying the email address. The link may have expired."
cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided." cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided."
doReaction: "Add reaction" doReaction: "Add reaction"
code: "Code"
tryReloadIfNotApplied: "If the settings are not reflected, please try refresh."
showUnreadNotificationsCount: "Show the number of unread notifications" showUnreadNotificationsCount: "Show the number of unread notifications"
showCatOnly: "Show only cats" showCatOnly: "Show only cats"
additionalPermissionsForFlash: "Allow to add permission to Play" additionalPermissionsForFlash: "Allow to add permission to Play"
@ -2623,3 +2625,16 @@ _imageCompressionMode:
noResizeCompress: "Compression without resize" noResizeCompress: "Compression without resize"
resizeCompressLossy: "Resize and lossy compression" resizeCompressLossy: "Resize and lossy compression"
noResizeCompressLossy: "Lossy compression without resize" noResizeCompressLossy: "Lossy compression without resize"
_dataSaver:
_media:
title: "Load media"
description: "Prevents images/videos from being loaded automatically. Hidden images/videos will be loaded when tapped."
_avatar:
title: "User icon image"
description: "Animation of user's icon images stops. Since animated images can be larger in file size than normal images, data traffic can be further reduced."
_urlPreview:
title: "URL preview thumbnail"
description: "URL preview thumbnail images will no longer load."
_code:
title: "Code Highlights"
description: "If code highlighting notation is used, such as MFM, it will not be loaded until tapped. Code highlighting requires loading the definition file for each language to be highlighted, but since these files are no longer loaded automatically, a reduction in communication volume can be expected."

20
locales/index.d.ts vendored
View file

@ -1277,6 +1277,8 @@ export interface Locale {
"signupPendingError": string; "signupPendingError": string;
"cwNotationRequired": string; "cwNotationRequired": string;
"doReaction": string; "doReaction": string;
"code": string;
"tryReloadIfNotApplied": string;
"showUnreadNotificationsCount": string; "showUnreadNotificationsCount": string;
"showCatOnly": string; "showCatOnly": string;
"additionalPermissionsForFlash": string; "additionalPermissionsForFlash": string;
@ -2850,6 +2852,24 @@ export interface Locale {
"resizeCompressLossy": string; "resizeCompressLossy": string;
"noResizeCompressLossy": string; "noResizeCompressLossy": string;
}; };
"_dataSaver": {
"_media": {
"title": string;
"description": string;
};
"_avatar": {
"title": string;
"description": string;
};
"_urlPreview": {
"title": string;
"description": string;
};
"_code": {
"title": string;
"description": string;
};
};
} }
declare const locales: { declare const locales: {
[lang: string]: Locale; [lang: string]: Locale;

View file

@ -1274,6 +1274,8 @@ useGroupedNotifications: "通知をグルーピングして表示する"
signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。"
cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。"
doReaction: "リアクションする" doReaction: "リアクションする"
code: "コード"
tryReloadIfNotApplied: "設定が反映されない場合はリロードをお試しください。"
showUnreadNotificationsCount: "未読の通知の数を表示する" showUnreadNotificationsCount: "未読の通知の数を表示する"
showCatOnly: "キャット付きのみ" showCatOnly: "キャット付きのみ"
additionalPermissionsForFlash: "Playへの追加許可" additionalPermissionsForFlash: "Playへの追加許可"
@ -2733,3 +2735,17 @@ _imageCompressionMode:
noResizeCompress: "縮小せず再圧縮する" noResizeCompress: "縮小せず再圧縮する"
resizeCompressLossy: "縮小して非可逆圧縮する" resizeCompressLossy: "縮小して非可逆圧縮する"
noResizeCompressLossy: "縮小せず非可逆圧縮する" noResizeCompressLossy: "縮小せず非可逆圧縮する"
_dataSaver:
_media:
title: "メディアの読み込み"
description: "画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。"
_avatar:
title: "アイコン画像"
description: "アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。"
_urlPreview:
title: "URLプレビューのサムネイル"
description: "URLプレビューのサムネイル画像が読み込まれなくなります。"
_code:
title: "コードハイライト"
description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"

View file

@ -1264,6 +1264,8 @@ useGroupedNotifications: "알림을 묶어서 표시"
signupPendingError: "메일 주소 확인중에 문제가 발생했어요. 링크의 유효기간이 지났을 수도 있어요." signupPendingError: "메일 주소 확인중에 문제가 발생했어요. 링크의 유효기간이 지났을 수도 있어요."
cwNotationRequired: "'내용 숨기기'를 체크했을 경우 주석을 작성해야 해요." cwNotationRequired: "'내용 숨기기'를 체크했을 경우 주석을 작성해야 해요."
doReaction: "리액션 추가" doReaction: "리액션 추가"
code: "코드"
tryReloadIfNotApplied: "설정이 반영되지 않으면 새로 고침을 시도해 보세요."
showUnreadNotificationsCount: "읽지 않은 알림 수 표시" showUnreadNotificationsCount: "읽지 않은 알림 수 표시"
showCatOnly: "고양이만 보기" showCatOnly: "고양이만 보기"
additionalPermissionsForFlash: "Play에 대한 추가 권한" additionalPermissionsForFlash: "Play에 대한 추가 권한"
@ -2620,3 +2622,16 @@ _imageCompressionMode:
noResizeCompress: "해상도를 축소하지 않고 압축" noResizeCompress: "해상도를 축소하지 않고 압축"
resizeCompressLossy: "해상도 축소 및 손실 압축" resizeCompressLossy: "해상도 축소 및 손실 압축"
noResizeCompressLossy: "해상도를 축소하지 않고 손실 압축" noResizeCompressLossy: "해상도를 축소하지 않고 손실 압축"
_dataSaver:
_media:
title: "미디어 불러오기"
description: "이미지/동영상이 자동으로 로드되는 것을 방지해요. 탭하면 숨겨진 이미지/동영상을 불러올 수 있어요."
_avatar:
title: "프로필 아이콘"
description: "움직이는 프로필 아이콘이 정적 이미지로 변경되어 표시돼요. 움직이는 이미지는 일반 이미지보다 파일 크기가 클 수 있기 때문에 데이터 통신량을 더욱 줄일 수 있어요."
_urlPreview:
title: "URL 미리보기 썸네일"
description: "URL 미리보기의 썸네일 이미지를 불러오지 않아요."
_code:
title: "코드 하이라이트"
description: "MFM 등에서 코드 하이라이트 기법을 사용하는 경우, 탭하기 전까지는 코드 하이라이트를 불러오지 않아요. 코드 하이라이트는 강조할 언어마다 해당 정의 파일을 불러와야 하지만, 이를 자동으로 불러오지 않기 때문에 통신량 감소를 기대할 수 있어요."

View file

@ -62,7 +62,7 @@ watch(() => props.lang, (to) => {
padding: 1em; padding: 1em;
margin: .5em 0; margin: .5em 0;
overflow: auto; overflow: auto;
border-radius: .3em; border-radius: 8px;
& pre, & pre,
& code { & code {

View file

@ -9,13 +9,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-if="!inline ?? true"/> <MkLoading v-if="!inline ?? true"/>
</template> </template>
<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code> <code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
<XCode v-else :code="code" :lang="lang"/> <XCode v-else-if="show" :code="code" :lang="lang"/>
<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
<div :class="$style.codePlaceholderContainer">
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
<div>{{ i18n.ts.clickToShow }}</div>
</div>
</button>
</Suspense> </Suspense>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent, ref } from 'vue';
import MkLoading from '@/components/global/MkLoading.vue'; import MkLoading from '@/components/global/MkLoading.vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
defineProps<{ defineProps<{
code: string; code: string;
@ -23,6 +31,8 @@ defineProps<{
inline?: boolean; inline?: boolean;
}>(); }>();
const show = ref(!defaultStore.state.dataSaver.code);
const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue')); const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'));
</script> </script>
@ -36,4 +46,25 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
padding: .1em; padding: .1em;
border-radius: .3em; border-radius: .3em;
} }
.codePlaceholderRoot {
display: block;
width: 100%;
background: none;
border: none;
outline: none;
font: inherit;
cursor: pointer;
box-sizing: border-box;
border-radius: 8px;
padding: 24px;
margin-top: 4px;
color: #D4D4D4;
background: #1E1E1E;
}
.codePlaceholderContainer {
text-align: center;
font-size: 0.8em;
}
</style> </style>

View file

@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
> >
<ImgWithBlurhash <ImgWithBlurhash
:hash="image.blurhash" :hash="image.blurhash"
:src="(defaultStore.state.enableDataSaverMode && hide) ? null : url" :src="(defaultStore.state.dataSaver.media && hide) ? null : url"
:forceBlurhash="hide" :forceBlurhash="hide"
:cover="hide || cover" :cover="hide || cover"
:alt="image.comment || image.name" :alt="image.comment || image.name"
@ -36,8 +36,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-if="hide"> <template v-if="hide">
<div :class="$style.hiddenText"> <div :class="$style.hiddenText">
<div :class="$style.hiddenTextWrapper"> <div :class="$style.hiddenTextWrapper">
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b> <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b> <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.dataSaver.media && image.size ? bytes(image.size) : i18n.ts.image }}</b>
<span v-if="controls" style="display: block;">{{ clickToShowMessage }}</span> <span v-if="controls" style="display: block;">{{ clickToShowMessage }}</span>
</div> </div>
</div> </div>
@ -86,7 +86,7 @@ if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation =
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000); let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const url = $computed(() => (props.raw || defaultStore.state.loadRawImages) const url = $computed(() => (props.raw || defaultStore.state.loadRawImages)
? props.image.url ? props.image.url
: (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation) : (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.media) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
? getStaticImageUrl(props.image.url) ? getStaticImageUrl(props.image.url)
: props.image.thumbnailUrl, : props.image.thumbnailUrl,
); );
@ -127,7 +127,7 @@ function resetTimer() {
// Plugin:register_note_view_interruptor 使watch // Plugin:register_note_view_interruptor 使watch
watch(() => props.image, () => { watch(() => props.image, () => {
hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore'); hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
}, { }, {
deep: true, deep: true,
immediate: true, immediate: true,

View file

@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="hide" :class="[$style.hidden, (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]" data-is-hidden="true" @click="onClick" @dblclick="defaultStore.state.nsfwOpenBehavior === 'doubleClick' ? hide = false : ''"> <div v-if="hide" :class="[$style.hidden, (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]" data-is-hidden="true" @click="onClick" @dblclick="defaultStore.state.nsfwOpenBehavior === 'doubleClick' ? hide = false : ''">
<!-- 注意dataSaverMode が有効になっている際にはhide false になるまでサムネイルや動画を読み込まないようにすること --> <!-- 注意dataSaverMode が有効になっている際にはhide false になるまでサムネイルや動画を読み込まないようにすること -->
<div :class="$style.sensitive"> <div :class="$style.sensitive">
<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b> <b v-if="video.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></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.enableDataSaverMode && 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>
<span>{{ clickToShowMessage }}</span> <span>{{ clickToShowMessage }}</span>
</div> </div>
</div> </div>
@ -45,7 +45,7 @@ const props = defineProps<{
video: Misskey.entities.DriveFile; video: Misskey.entities.DriveFile;
}>(); }>();
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? 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'));
let clickToShowMessage = $computed(() => let clickToShowMessage = $computed(() =>
defaultStore.state.nsfwOpenBehavior === 'click' ? i18n.ts.clickToShow defaultStore.state.nsfwOpenBehavior === 'click' ? i18n.ts.clickToShow

View file

@ -51,7 +51,7 @@ const bgCss = bg.toRgbString();
let playAnimation = $ref(true); let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false; if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000); let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const avatarUrl = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation) const avatarUrl = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
? getStaticImageUrl(`/avatar/@${props.username}@${props.host}`) ? getStaticImageUrl(`/avatar/@${props.username}@${props.host}`)
: `/avatar/@${props.username}@${props.host}`, : `/avatar/@${props.username}@${props.host}`,
); );

View file

@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<div v-else> <div v-else>
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" :class="$style.thumbnail" :style="defaultStore.state.enableDataSaverMode ? '' : `background-image: url('${thumbnail}')`"> <div v-if="thumbnail" :class="$style.thumbnail" :style="defaultStore.state.dataSaver.urlPreview ? '' : `background-image: url('${thumbnail}')`">
</div> </div>
<article :class="$style.body"> <article :class="$style.body">
<header :class="$style.header"> <header :class="$style.header">

View file

@ -94,7 +94,7 @@ const bound = $computed(() => props.link
let playAnimation = $ref(true); let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false; if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000); let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation) const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
? getStaticImageUrl(props.user.avatarUrl) ? getStaticImageUrl(props.user.avatarUrl)
: props.user.avatarUrl); : props.user.avatarUrl);

View file

@ -117,7 +117,7 @@ const bound = $computed(() => props.link
let playAnimation = $ref(true); let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false; if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000); let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation) const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
? getStaticImageUrl(props.user.avatarUrl) ? getStaticImageUrl(props.user.avatarUrl)
: props.user.avatarUrl); : props.user.avatarUrl);

View file

@ -93,7 +93,7 @@ const bound = $computed(() => props.link
let playAnimation = $ref(true); let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false; if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000); let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation) const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
? getStaticImageUrl(props.user.avatarUrl) ? getStaticImageUrl(props.user.avatarUrl)
: props.user.avatarUrl); : props.user.avatarUrl);

View file

@ -155,7 +155,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch> <MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch> <MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
<MkSwitch v-model="showUnreadNotificationsCount">{{ i18n.ts.showUnreadNotificationsCount }}</MkSwitch> <MkSwitch v-model="showUnreadNotificationsCount">{{ i18n.ts.showUnreadNotificationsCount }}</MkSwitch>
<MkSwitch v-model="enableDataSaverMode">{{ i18n.ts.dataSaver }}</MkSwitch>
</div> </div>
<div> <div>
<MkRadios v-model="emojiStyle"> <MkRadios v-model="emojiStyle">
@ -274,6 +273,37 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</FormSection> </FormSection>
<FormSection>
<template #label>{{ i18n.ts.dataSaver }} <span class="_beta">CherryPick</span></template>
<div class="_gaps_m">
<MkInfo>{{ i18n.ts.tryReloadIfNotApplied }}</MkInfo>
<div class="_buttons">
<MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
<MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
</div>
<div class="_gaps_m">
<MkSwitch v-model="dataSaver.media">
{{ i18n.ts._dataSaver._media.title }}
<template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.avatar">
{{ i18n.ts._dataSaver._avatar.title }}
<template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.urlPreview">
{{ i18n.ts._dataSaver._urlPreview.title }}
<template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.code">
{{ i18n.ts._dataSaver._code.title }}
<template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
</MkSwitch>
</div>
</div>
</FormSection>
<FormSection> <FormSection>
<template #label>{{ i18n.ts.other }}</template> <template #label>{{ i18n.ts.other }}</template>
@ -304,6 +334,7 @@ import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue'; import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue'; import MkLink from '@/components/MkLink.vue';
import MkInfo from '@/components/MkInfo.vue';
import { langs } from '@/config.js'; import { langs } from '@/config.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -313,11 +344,11 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { globalEvents } from '@/events.js'; import { globalEvents } from '@/events.js';
import { claimAchievement } from '@/scripts/achievements.js'; import { claimAchievement } from '@/scripts/achievements.js';
import MkInfo from '@/components/MkInfo.vue';
const lang = ref(miLocalStorage.getItem('lang')); const lang = ref(miLocalStorage.getItem('lang'));
// const fontSize = ref(miLocalStorage.getItem('fontSize')); // const fontSize = ref(miLocalStorage.getItem('fontSize'));
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
const dataSaver = ref(defaultStore.state.dataSaver);
const fontSizeBefore = ref(miLocalStorage.getItem('fontSize')); const fontSizeBefore = ref(miLocalStorage.getItem('fontSize'));
const useBoldFont = ref(miLocalStorage.getItem('useBoldFont')); const useBoldFont = ref(miLocalStorage.getItem('useBoldFont'));
@ -354,7 +385,6 @@ const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('dis
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds')); const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages')); const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia')); const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode'));
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab')); const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
const nsfw = computed(defaultStore.makeGetterSetter('nsfw')); const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm')); const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
@ -440,7 +470,6 @@ watch([
keepScreenOn, keepScreenOn,
disableStreamingTimeline, disableStreamingTimeline,
showUnreadNotificationsCount, showUnreadNotificationsCount,
enableDataSaverMode,
enableAbsoluteTime, enableAbsoluteTime,
enableMarkByDate, enableMarkByDate,
showSubNoteFooterButton, showSubNoteFooterButton,
@ -528,6 +557,24 @@ function testNotification(): void {
}, 300); }, 300);
} }
function enableAllDataSaver() {
const g = defaultStore.state.dataSaver;
Object.keys(g).forEach((key) => { g[key] = true; });
dataSaver.value = g;
}
function disableAllDataSaver() {
const g = defaultStore.state.dataSaver;
Object.keys(g).forEach((key) => { g[key] = false; });
dataSaver.value = g;
}
watch(dataSaver, (to) => {
defaultStore.set('dataSaver', to);
}, {
deep: true,
});
onMounted(() => { onMounted(() => {
if (fontSizeBefore.value == null) { if (fontSizeBefore.value == null) {
fontSizeBefore.value = fontSize.value as string; fontSizeBefore.value = fontSize.value as string;

View file

@ -71,7 +71,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'advancedMfm', 'advancedMfm',
'loadRawImages', 'loadRawImages',
'imageNewTab', 'imageNewTab',
'enableDataSaverMode', 'dataSaver',
'disableShowingAnimatedImages', 'disableShowingAnimatedImages',
'showingAnimatedImages', 'showingAnimatedImages',
'emojiStyle', 'emojiStyle',

View file

@ -69,7 +69,7 @@ if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation =
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000); let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
function thumbnail(image: Misskey.entities.DriveFile): string { function thumbnail(image: Misskey.entities.DriveFile): string {
return (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation) return (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.media) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
? getStaticImageUrl(image.url) ? getStaticImageUrl(image.url)
: image.thumbnailUrl; : image.thumbnailUrl;
} }

View file

@ -232,10 +232,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: false, default: false,
}, },
enableDataSaverMode: {
where: 'device',
default: false,
},
disableShowingAnimatedImages: { disableShowingAnimatedImages: {
where: 'device', where: 'device',
default: window.matchMedia('(prefers-reduced-motion)').matches, default: window.matchMedia('(prefers-reduced-motion)').matches,
@ -424,6 +420,15 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'deviceAccount', where: 'deviceAccount',
default: false, default: false,
}, },
dataSaver: {
where: 'device',
default: {
media: false,
avatar: false,
urlPreview: false,
code: false,
} as Record<string, boolean>,
},
sound_masterVolume: { sound_masterVolume: {
where: 'device', where: 'device',

View file

@ -75,7 +75,7 @@ let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false; if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000); let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const thumbnail = (image: any): string => { const thumbnail = (image: any): string => {
return (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation) return (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.media) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
? getStaticImageUrl(image.url) ? getStaticImageUrl(image.url)
: image.thumbnailUrl; : image.thumbnailUrl;
}; };

View file

@ -21,7 +21,17 @@ vi.stubGlobal('WebSocket', class WebSocket extends EventTarget { static CLOSING
vi.mock('@/store.js', () => { vi.mock('@/store.js', () => {
return { return {
defaultStore: { defaultStore: {
state: {}, state: {
// なんかtestがうまいこと動かないのでここに書く
dataSaver: {
media: false,
avatar: false,
urlPreview: false,
code: false,
},
},
}, },
}; };
}); });