Merge branch 'kokonect-link:develop' into patch-1

This commit is contained in:
아르페 2023-12-01 18:38:48 +09:00 committed by GitHub
commit 4f6091b2d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 272 additions and 69 deletions

View file

@ -20,7 +20,7 @@ jobs:
sudo dpkg -i dockle.deb
- run: |
cp .config/docker_example.env .config/docker.env
cp ./docker-compose.yml.example ./docker-compose.yml
cp ./docker-compose_example.yml ./docker-compose.yml
- run: |
docker compose up -d web
docker tag "$(docker compose images web | awk 'OFS=":" {print $4}' | tail -n +2)" cherrypick-web:latest

View file

@ -34,29 +34,35 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGE
Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2023xx](CHANGELOG.md#2023xx) 문서를 참고하십시오.
### General
- Change: 노트를 번역할 때 유저가 고양이로 설정되어 있으면 nyaize를 적용
- Feat: 리모트 서버의 이모지를 즉시 가져올 수 있음 ([pikokr/cherrypicnic@03d536c0](https://github.com/pikokr/cherrypicnic/commit/03d536c00212f2dfbebecf75e5d58e0ddb749444), [pikokr/cherrypicnic@8a2d6f3b](https://github.com/pikokr/cherrypicnic/commit/8a2d6f3b518fc13a6c32364780fba3be5eea3e5d))
- Feat: 아이콘 장식을 여러 개 겹칠 수 있음 ([Secineralyr/misskey.dream@d929c8bf](https://github.com/Secineralyr/misskey.dream/commit/d929c8bf97add7fac64c12e0bcdfaa164031f864))
- Feat: 아이콘 장식을 세부 조정할 수 있음 ([Secineralyr/misskey.dream@b3299181](https://github.com/Secineralyr/misskey.dream/commit/b329918194f1991c84633361d8a1319cf203641c), [Secineralyr/misskey.dream@1a9642bb](https://github.com/Secineralyr/misskey.dream/commit/1a9642bb9087a256522767e113c3bbfa87ec2e47))
- 위치 조정
- 크기 조정
- 불투명도 조정
- 위치, 크기, 불투명도를 추가로 조정할 수 있습니다.
- Feat: 노트를 클릭하여 자세히 볼 수 있음
- Change: 노트를 번역할 때 유저가 고양이로 설정되어 있으면 nyaize를 적용
- Revert: 사용자 통계 표시 기능 제거 ([MisskeyIO/misskey@114c7fe6](https://github.com/MisskeyIO/misskey/commit/114c7fe6b37dd6bddbcd9d92406f8b13bf688e8b))
### Client
- Feat: 데이터 절약 모드로 코드 하이라이트 로드를 줄일 수 있음 (misskey-dev/misskey#12526)
- Feat: InstanceTicker를 클릭해 노트를 자세히 볼 수 있음
- 리모트에서 수신된 노트인 경우, '리모트에서 보기'로 작동함
- 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: 클라이언트 언어와 노트 본문의 언어가 같으면 번역 버튼을 표시하지 않음
- Enhance: 진동 개선
- 진동을 사용할 수 없는 환경에서 스위치를 조작할 수 없도록 비활성화
- 진동을 사용할 수 없는 이유를 보다 명확하게 표시하도록 개선
- Enhance: 진동 기능 개선
- 진동 기능을 사용할 수 없는 환경에서 스위치를 조작할 수 없도록 비활성화
- 진동 기능을 사용할 수 없는 이유를 보다 명확하게 표시하도록 개선
- Enhance: 데이터 절약 모드 적용 범위를 개별적으로 설정할 수 있음 (misskey-dev/misskey#12526)
- 기존 데이터 절약 모드 설정이 재설정됩니다.
- Enhance: 컴포넌트만 새로 고쳐도 적용할 수 있는 설정은 페이지를 새로 고치지 않고 설정을 반영함
- 각 기능이 적용되는 컴포넌트(타임라인, 알림)에 따라 해당 컴포넌트만 새로 로드됩니다.
- Fix: '모달 배경색 제거' 옵션이 이모지 피커에 반영되지 않음
- Fix: 열람 주의로 설정된 노트의 반응이 더 보기를 눌러야 표시됨
- Fix: 열람 주의로 설정된 노트의 리액션이 '더 보기'를 눌러야 표시됨
- Fix: 채널 이름이 긴 경우 게시 양식 표시가 깨지는 문제 (misskey-dev/misskey#12524)
### Server
- Enhance: (dev) 개발 모드에서 locale 및 유형 정의가 자동으로 재생성되도록 (misskey-dev/misskey#12481)
- Enhance: (dev) 개발 모드에서 locale 및 유형 정의가 자동으로 재생성 (misskey-dev/misskey#12481)
---

View file

@ -512,7 +512,6 @@ share: "Share"
notFound: "Not found"
notFoundDescription: "No page corresponding to this URL could be found."
uploadFolder: "Default folder for uploads"
cacheClear: "Clear cache"
markAsReadAllNotifications: "Mark all notifications as read"
markAsReadAllUnreadNotes: "Mark all notes as read"
markAsReadAllTalkMessages: "Mark all messages as read"
@ -1268,6 +1267,8 @@ useGroupedNotifications: "Display grouped notifications"
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."
doReaction: "Add reaction"
code: "Code"
tryReloadIfNotApplied: "If the settings are not reflected, please try refresh."
showUnreadNotificationsCount: "Show the number of unread notifications"
showCatOnly: "Show only cats"
additionalPermissionsForFlash: "Allow to add permission to Play"
@ -2623,3 +2624,16 @@ _imageCompressionMode:
noResizeCompress: "Compression without resize"
resizeCompressLossy: "Resize and lossy compression"
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."

21
locales/index.d.ts vendored
View file

@ -515,7 +515,6 @@ export interface Locale {
"notFound": string;
"notFoundDescription": string;
"uploadFolder": string;
"cacheClear": string;
"markAsReadAllNotifications": string;
"markAsReadAllUnreadNotes": string;
"markAsReadAllTalkMessages": string;
@ -1277,6 +1276,8 @@ export interface Locale {
"signupPendingError": string;
"cwNotationRequired": string;
"doReaction": string;
"code": string;
"tryReloadIfNotApplied": string;
"showUnreadNotificationsCount": string;
"showCatOnly": string;
"additionalPermissionsForFlash": string;
@ -2850,6 +2851,24 @@ export interface Locale {
"resizeCompressLossy": 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: {
[lang: string]: Locale;

View file

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

View file

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

View file

@ -1,6 +1,6 @@
{
"name": "cherrypick",
"version": "4.5.2",
"version": "4.6.0-beta.1",
"basedMisskeyVersion": "2023.12.0-beta.1",
"codename": "nasubi",
"repository": {

View file

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

View file

@ -9,13 +9,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-if="!inline ?? true"/>
</template>
<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>
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, ref } from 'vue';
import MkLoading from '@/components/global/MkLoading.vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
defineProps<{
code: string;
@ -23,6 +31,8 @@ defineProps<{
inline?: boolean;
}>();
const show = ref(!defaultStore.state.dataSaver.code);
const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'));
</script>
@ -36,4 +46,25 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
padding: .1em;
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>

View file

@ -101,6 +101,8 @@ function close() {
vertical-align: bottom;
height: 100px;
border-radius: 10px;
padding: 10px;
box-sizing: border-box;
&:hover {
color: var(--accent);

View file

@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<ImgWithBlurhash
:hash="image.blurhash"
:src="(defaultStore.state.enableDataSaverMode && hide) ? null : url"
:src="(defaultStore.state.dataSaver.media && hide) ? null : url"
:forceBlurhash="hide"
:cover="hide || cover"
:alt="image.comment || image.name"
@ -36,8 +36,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-if="hide">
<div :class="$style.hiddenText">
<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-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</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.dataSaver.media && image.size ? bytes(image.size) : i18n.ts.image }}</b>
<span v-if="controls" style="display: block;">{{ clickToShowMessage }}</span>
</div>
</div>
@ -86,15 +86,17 @@ if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation =
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const url = $computed(() => (props.raw || defaultStore.state.loadRawImages)
? 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)
: props.image.thumbnailUrl,
);
let clickToShowMessage = $computed(() =>
defaultStore.state.nsfwOpenBehavior === 'click' ? i18n.ts.clickToShow
: defaultStore.state.nsfwOpenBehavior === 'doubleClick' ? i18n.ts.doubleClickToShow
: '',
let clickToShowMessage = $computed(() => defaultStore.state.nsfwOpenBehavior === 'click'
? i18n.ts.clickToShow
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
: defaultStore.state.nsfwOpenBehavior === 'doubleClick'
? i18n.ts.doubleClickToShow
: '',
);
function onClick(ev: MouseEvent) {
@ -127,7 +129,7 @@ function resetTimer() {
// Plugin:register_note_view_interruptor 使watch
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,
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 : ''">
<!-- 注意dataSaverMode が有効になっている際にはhide false になるまでサムネイルや動画を読み込まないようにすること -->
<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-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</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.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
<span>{{ clickToShowMessage }}</span>
</div>
</div>
@ -45,12 +45,14 @@ const props = defineProps<{
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(() =>
defaultStore.state.nsfwOpenBehavior === 'click' ? i18n.ts.clickToShow
: defaultStore.state.nsfwOpenBehavior === 'doubleClick' ? i18n.ts.doubleClickToShow
: '',
let clickToShowMessage = $computed(() => defaultStore.state.nsfwOpenBehavior === 'click'
? i18n.ts.clickToShow
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
: defaultStore.state.nsfwOpenBehavior === 'doubleClick'
? i18n.ts.doubleClickToShow
: '',
);
function onClick(ev: MouseEvent) {

View file

@ -51,7 +51,7 @@ const bgCss = bg.toRgbString();
let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
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}`)
: `/avatar/@${props.username}@${props.host}`,
);

View file

@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkTime :time="note.createdAt" :mode="defaultStore.state.enableAbsoluteTime ? 'absolute' : 'relative'" colored/>
</MkA>
</div>
<div :style="$style.info"><MkInstanceTicker v-if="showTicker" :instance="note.user.instance"/></div>
<div :style="$style.info"><MkInstanceTicker v-if="showTicker" :instance="note.user.instance" @click.stop="showOnRemote"/></div>
</div>
</header>
</template>
@ -56,6 +56,7 @@ import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js';
import { defaultStore } from '@/store.js';
import { deepClone } from '@/scripts/clone.js';
import { useRouter } from '@/router.js';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
const props = defineProps<{
@ -66,6 +67,13 @@ const mock = inject<boolean>('mock', false);
let note = $ref(deepClone(props.note));
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && note.user.instance);
const router = useRouter();
function showOnRemote() {
if (props.note.url ?? props.note.uri === undefined) router.push(notePage(note));
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
else window.open(props.note.url ?? props.note.uri);
}
</script>
<style lang="scss" module>

View file

@ -37,6 +37,7 @@ import { infoImageUrl } from '@/instance.js';
import { defaultStore } from '@/store.js';
import { mainRouter } from '@/router.js';
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
import { globalEvents } from '@/events.js';
const props = defineProps<{
excludeTypes?: typeof notificationTypes[number][];
@ -82,6 +83,8 @@ let connection;
onMounted(() => {
connection = useStream().useChannel('main');
connection.on('notification', onNotification);
globalEvents.on('reloadNotification', () => reload());
});
onActivated(() => {

View file

@ -1162,8 +1162,9 @@ defineExpose({
.visibility {
overflow: clip;
text-overflow: ellipsis;
white-space: nowrap;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 210px;
&:enabled {
> .headerRightButtonText {

View file

@ -1231,8 +1231,9 @@ defineExpose({
.visibility {
overflow: clip;
text-overflow: ellipsis;
white-space: nowrap;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 210px;
&:enabled {
> .headerRightButtonText {

View file

@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, watch, onUnmounted, provide } from 'vue';
import { computed, watch, onUnmounted, provide, onMounted } from 'vue';
import { Connection } from 'cherrypick-js/built/streaming.js';
import MkNotes from '@/components/MkNotes.vue';
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
@ -29,6 +29,7 @@ import { defaultStore } from '@/store.js';
import { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { vibrate } from '@/scripts/vibrate.js';
import { globalEvents } from '@/events.js';
const props = withDefaults(defineProps<{
src: string;
@ -269,6 +270,10 @@ watch(() => [props.list, props.antenna, props.channel, props.role], refreshEndpo
//
refreshEndpointAndChannel();
onMounted(() => {
globalEvents.on('reloadTimeline', () => reloadTimeline());
});
onUnmounted(() => {
disconnectChannel();
});

View file

@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<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">
<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>
<article :class="$style.body">
<header :class="$style.header">

View file

@ -94,7 +94,7 @@ const bound = $computed(() => props.link
let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
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)
: props.user.avatarUrl);

View file

@ -117,7 +117,7 @@ const bound = $computed(() => props.link
let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
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)
: props.user.avatarUrl);

View file

@ -93,7 +93,7 @@ const bound = $computed(() => props.link
let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
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)
: props.user.avatarUrl);

View file

@ -212,7 +212,7 @@ export const navbarItemDef = reactive({
to: `/@${$i?.username}`,
},
cacheClear: {
title: i18n.ts.cacheClear,
title: i18n.ts.clearCache,
icon: 'ti ti-trash',
action: (ev) => {
clearCache();

View file

@ -155,7 +155,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
<MkSwitch v-model="showUnreadNotificationsCount">{{ i18n.ts.showUnreadNotificationsCount }}</MkSwitch>
<MkSwitch v-model="enableDataSaverMode">{{ i18n.ts.dataSaver }}</MkSwitch>
</div>
<div>
<MkRadios v-model="emojiStyle">
@ -274,6 +273,37 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</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>
<template #label>{{ i18n.ts.other }}</template>
@ -304,6 +334,7 @@ import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue';
import MkInfo from '@/components/MkInfo.vue';
import { langs } from '@/config.js';
import { defaultStore } from '@/store.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 { globalEvents } from '@/events.js';
import { claimAchievement } from '@/scripts/achievements.js';
import MkInfo from '@/components/MkInfo.vue';
const lang = ref(miLocalStorage.getItem('lang'));
// const fontSize = ref(miLocalStorage.getItem('fontSize'));
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
const dataSaver = ref(defaultStore.state.dataSaver);
const fontSizeBefore = ref(miLocalStorage.getItem('fontSize'));
const useBoldFont = ref(miLocalStorage.getItem('useBoldFont'));
@ -334,6 +365,14 @@ async function reloadAsk() {
} else globalEvents.emit('hasRequireRefresh', true);
}
function reloadTimeline() {
globalEvents.emit('reloadTimeline');
}
function reloadNotification() {
globalEvents.emit('reloadNotification');
}
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
@ -354,7 +393,6 @@ const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('dis
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode'));
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
@ -426,32 +464,41 @@ watch([
// fontSize,
useBoldFont,
useSystemFont,
enableInfiniteScroll,
squareAvatars,
showGapBetweenNotesInTimeline,
overridedDeviceKind,
keepScreenOn,
disableStreamingTimeline,
showUnreadNotificationsCount,
showFixedPostFormInReplies,
showingAnimatedImages,
], async () => {
await reloadAsk();
});
watch([
enableInfiniteScroll,
hideAvatarsInNote,
showNoteActionsOnlyHover,
showGapBetweenNotesInTimeline,
instanceTicker,
overridedDeviceKind,
mediaListWithOneImageAppearance,
reactionsDisplaySize,
limitWidthOfReaction,
highlightSensitiveMedia,
keepScreenOn,
disableStreamingTimeline,
showUnreadNotificationsCount,
enableDataSaverMode,
enableAbsoluteTime,
enableMarkByDate,
showSubNoteFooterButton,
infoButtonForNoteActionsEnabled,
showReplyInNotification,
renoteQuoteButtonSeparation,
showFixedPostFormInReplies,
showingAnimatedImages,
allMediaNoteCollapse,
], async () => {
await reloadAsk();
], () => {
reloadTimeline();
});
watch([
showReplyInNotification,
], () => {
reloadNotification();
});
const emojiIndexLangs = ['en-US'];
@ -528,6 +575,24 @@ function testNotification(): void {
}, 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(() => {
if (fontSizeBefore.value == null) {
fontSizeBefore.value = fontSize.value as string;

View file

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

View file

@ -69,7 +69,7 @@ if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation =
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
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)
: image.thumbnailUrl;
}

View file

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

View file

@ -75,7 +75,7 @@ let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
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)
: image.thumbnailUrl;
};

View file

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