Merge branch 'develop' into enhance-migration

This commit is contained in:
Namekuji 2023-04-17 03:46:24 -04:00 committed by GitHub
commit c879cddfd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 146 additions and 31 deletions

View File

@ -13,6 +13,9 @@
--> -->
## 13.x.x (unreleased) ## 13.x.x (unreleased)
### NOTE
- Node.js 18.6.0以上が必要になりました
### General ### General
- アカウントの引っ越し(フォロワー引き継ぎ)に対応 - アカウントの引っ越し(フォロワー引き継ぎ)に対応
* 一度引っ越したアカウントは利用に制限がかかります * 一度引っ越したアカウントは利用に制限がかかります
@ -23,6 +26,7 @@
デスクトップ表示ではusernameの右側のボタンからも追加可能 デスクトップ表示ではusernameの右側のボタンからも追加可能
### Client ### Client
- 通知の表示をカスタマイズできるように
- コントロールパネルのカスタム絵文字ページおよびaboutのカスタム絵文字の検索インプットで、`:emojiname1::emojiname2:`のように検索して絵文字を検索できるように - コントロールパネルのカスタム絵文字ページおよびaboutのカスタム絵文字の検索インプットで、`:emojiname1::emojiname2:`のように検索して絵文字を検索できるように
* 絵文字ピッカーから入力可能になります * 絵文字ピッカーから入力可能になります
- データセーバーモードを追加 - データセーバーモードを追加
@ -33,6 +37,7 @@
### Server ### Server
- Fix: エクスポートデータの拡張子がunknownになる問題を修正 - Fix: エクスポートデータの拡張子がunknownになる問題を修正
- Fix: Content-Dispositionのパースでエラーが発生した場合にダウンロードが完了しない問題を修正 - Fix: Content-Dispositionのパースでエラーが発生した場合にダウンロードが完了しない問題を修正
- Fix: API: i/update avatarIdとbannerIdにnullを渡した時、画像がリセットされない問題を修正
## 13.11.3 ## 13.11.3

View File

@ -1001,6 +1001,15 @@ accountMoved: "このユーザーは新しいアカウントに引っ越しま
forceShowAds: "常に広告を表示する" forceShowAds: "常に広告を表示する"
addMemo: "メモを追加" addMemo: "メモを追加"
editMemo: "メモを編集" editMemo: "メモを編集"
notificationDisplay: "通知の表示"
leftTop: "左上"
rightTop: "右上"
leftBottom: "左下"
rightBottom: "右下"
stackAxis: "スタック方向"
vertical: "縦"
horizontal: "横"
position: "位置"
_accountMigration: _accountMigration:
moveTo: "このアカウントを新しいアカウントに引っ越す" moveTo: "このアカウントを新しいアカウントに引っ越す"

View File

@ -221,6 +221,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
updates.avatarId = avatar.id; updates.avatarId = avatar.id;
updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar'); updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
updates.avatarBlurhash = avatar.blurhash; updates.avatarBlurhash = avatar.blurhash;
} else if (ps.avatarId === null) {
updates.avatarId = null;
updates.avatarUrl = null;
updates.avatarBlurhash = null;
} }
if (ps.bannerId) { if (ps.bannerId) {
@ -232,6 +236,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
updates.bannerId = banner.id; updates.bannerId = banner.id;
updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner); updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
updates.bannerBlurhash = banner.blurhash; updates.bannerBlurhash = banner.blurhash;
} else if (ps.bannerId === null) {
updates.bannerId = null;
updates.bannerUrl = null;
updates.bannerBlurhash = null;
} }
if (ps.pinnedPageId) { if (ps.pinnedPageId) {

View File

@ -508,7 +508,6 @@ describe('ユーザー', () => {
}; };
assert.deepStrictEqual(response, expected, inspect(parameters)); assert.deepStrictEqual(response, expected, inspect(parameters));
if (1) return; // BUG 521eb95 以降アバターのリセットができない。
const parameters2 = { avatarId: null }; const parameters2 = { avatarId: null };
const response2 = await successfulApiCall({ endpoint: 'i/update', parameters: parameters2, user: alice }); const response2 = await successfulApiCall({ endpoint: 'i/update', parameters: parameters2, user: alice });
const expected2 = { const expected2 = {
@ -534,7 +533,6 @@ describe('ユーザー', () => {
}; };
assert.deepStrictEqual(response, expected, inspect(parameters)); assert.deepStrictEqual(response, expected, inspect(parameters));
if (1) return; // BUG 521eb95 以降バナーのリセットができない。
const parameters2 = { bannerId: null }; const parameters2 = { bannerId: null };
const response2 = await successfulApiCall({ endpoint: 'i/update', parameters: parameters2, user: alice }); const response2 = await successfulApiCall({ endpoint: 'i/update', parameters: parameters2, user: alice });
const expected2 = { const expected2 = {

View File

@ -38,6 +38,7 @@ fs.readFile(
path.resolve(__dirname, '../../..', arg) path.resolve(__dirname, '../../..', arg)
) )
) )
.map((path) => path.replace(/(?:(?<=\.stories)\.(?:impl|meta)|\.msw)(?=\.ts$)/g, ''))
.map((path) => (path.startsWith('.') ? path : `./${path}`)) .map((path) => (path.startsWith('.') ? path : `./${path}`))
); );
if ( if (

View File

@ -247,6 +247,10 @@ watch($$(text), () => {
checkMissingMention(); checkMissingMention();
}, { immediate: true }); }, { immediate: true });
watch($$(visibility), () => {
checkMissingMention();
}, { immediate: true });
watch($$(visibleUsers), () => { watch($$(visibleUsers), () => {
checkMissingMention(); checkMissingMention();
}, { }, {

View File

@ -92,6 +92,26 @@
</div> </div>
</FormSection> </FormSection>
<FormSection>
<template #label>{{ i18n.ts.notificationDisplay }}</template>
<div class="_gaps_m">
<MkRadios v-model="notificationPosition">
<template #label>{{ i18n.ts.position }}</template>
<option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option>
<option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option>
<option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option>
<option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option>
</MkRadios>
<MkRadios v-model="notificationStackAxis">
<template #label>{{ i18n.ts.stackAxis }}</template>
<option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option>
<option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
</MkRadios>
</div>
</FormSection>
<FormSection> <FormSection>
<MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch> <MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch>
</FormSection> </FormSection>
@ -181,6 +201,8 @@ const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('
const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars')); const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode')); const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance')); const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
watch(lang, () => { watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string); miLocalStorage.setItem('lang', lang.value as string);

View File

@ -314,6 +314,14 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3', default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3',
}, },
notificationPosition: {
where: 'device',
default: 'rightBottom' as 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom',
},
notificationStackAxis: {
where: 'device',
default: 'horizontal' as 'vertical' | 'horizontal',
},
})); }));
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期

View File

@ -10,14 +10,16 @@
<XUpload v-if="uploads.length > 0"/> <XUpload v-if="uploads.length > 0"/>
<TransitionGroup <TransitionGroup
tag="div" :class="$style.notifications" tag="div" :class="[$style.notifications, $style[`notificationsPosition-${defaultStore.state.notificationPosition}`], $style[`notificationsStackAxis-${defaultStore.state.notificationStackAxis}`]]"
:move-class="defaultStore.state.animation ? $style.transition_notification_move : ''" :move-class="defaultStore.state.animation ? $style.transition_notification_move : ''"
:enter-active-class="defaultStore.state.animation ? $style.transition_notification_enterActive : ''" :enter-active-class="defaultStore.state.animation ? $style.transition_notification_enterActive : ''"
:leave-active-class="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''" :leave-active-class="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''"
:enter-from-class="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''" :enter-from-class="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''"
:leave-to-class="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''" :leave-to-class="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''"
> >
<XNotification v-for="notification in notifications" :key="notification.id" :notification="notification" :class="$style.notification"/> <div v-for="notification in notifications" :key="notification.id" :class="$style.notification">
<XNotification :notification="notification"/>
</div>
</TransitionGroup> </TransitionGroup>
<XStreamIndicator/> <XStreamIndicator/>
@ -30,7 +32,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent, ref } from 'vue';
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
import { swInject } from './sw-inject'; import { swInject } from './sw-inject';
import XNotification from './notification.vue'; import XNotification from './notification.vue';
@ -85,7 +87,10 @@ if ($i) {
.transition_notification_leaveActive { .transition_notification_leaveActive {
transition: opacity 0.3s, transform 0.3s !important; transition: opacity 0.3s, transform 0.3s !important;
} }
.transition_notification_enterFrom, .transition_notification_enterFrom {
opacity: 0;
transform: translateX(250px);
}
.transition_notification_leaveTo { .transition_notification_leaveTo {
opacity: 0; opacity: 0;
transform: translateX(-250px); transform: translateX(-250px);
@ -94,35 +99,90 @@ if ($i) {
.notifications { .notifications {
position: fixed; position: fixed;
z-index: 3900000; z-index: 3900000;
left: 0;
width: 250px;
top: 32px;
padding: 0 32px;
pointer-events: none;
container-type: inline-size;
}
.notification {
& + .notification {
margin-top: 8px;
}
}
@media (max-width: 500px) {
.notifications {
top: initial;
bottom: calc(var(--minBottomSpacing) + var(--margin));
padding: 0 var(--margin); padding: 0 var(--margin);
pointer-events: none;
display: flex; display: flex;
flex-direction: column-reverse;
&.notificationsPosition-leftTop {
top: var(--margin);
left: 0;
} }
&.notificationsPosition-rightTop {
top: var(--margin);
right: 0;
}
&.notificationsPosition-leftBottom {
bottom: calc(var(--minBottomSpacing) + var(--margin));
left: 0;
}
&.notificationsPosition-rightBottom {
bottom: calc(var(--minBottomSpacing) + var(--margin));
right: 0;
}
&.notificationsStackAxis-vertical {
width: 250px;
&.notificationsPosition-leftTop,
&.notificationsPosition-rightTop {
flex-direction: column;
.notification {
& + .notification {
margin-top: 8px;
}
}
}
&.notificationsPosition-leftBottom,
&.notificationsPosition-rightBottom {
flex-direction: column-reverse;
.notification { .notification {
& + .notification { & + .notification {
margin-top: 0;
margin-bottom: 8px; margin-bottom: 8px;
} }
} }
}
}
&.notificationsStackAxis-horizontal {
width: 100%;
&.notificationsPosition-leftTop,
&.notificationsPosition-leftBottom {
flex-direction: row;
.notification {
& + .notification {
margin-left: 8px;
}
}
}
&.notificationsPosition-rightTop,
&.notificationsPosition-rightBottom {
flex-direction: row-reverse;
.notification {
& + .notification {
margin-right: 8px;
}
}
}
.notification {
width: 250px;
flex-shrink: 0;
}
}
}
.notification {
container-type: inline-size;
} }
</style> </style>