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

release: v2024.5.0-kakurega.1.38.0 #151

Merged
merged 6 commits into from
Jun 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG_KAKUREGA.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
## 1.38.0
Release: 2024/06/29
Base: 2024.5.0

### 新機能/機能改善

#### ノートの自己消滅の初期値を設定できるように
投稿フォームで「ノートの自己消滅」を有効にした際に設定される初期値を設定できます。
設定 → 全般 → 投稿フォーム から設定できます。
![image](https://media.kakurega.app/static/misskey/95d273de-8471-4d7d-997e-cd4307007e68.png)

#### ノートの自己消滅をデフォルトで有効にできるように
有効にすると、投稿フォームを開いた際にデフォルトで「ノートの自己消滅」が有効になります。
設定 → 全般 → 投稿フォーム から設定できます。

#### 投稿フォームのノート自己消滅のUIを改善
ノートの自己消滅の設定欄を折り畳めるようになりました。
ノートの自己消滅がデフォルトで有効になっている場合は、初期状態で折りたたまれた状態になります。
![image](https://media.kakurega.app/static/misskey/8f77388f-1c73-4e5d-9f7d-9d6d0e6359c1.png)

#### ミュート期限を追加
これまでの項目に加え、「6時間」「12時間」が追加されました。

## 1.37.0
Release: 2024/05/22
Base: 2024.3.1
8 changes: 8 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
@@ -5364,6 +5364,14 @@ export interface Locale extends ILocale {
* デフォルトでノートが自己消滅するように
*/
"defaultScheduledNoteDelete": string;
/**
* ノートの自己消滅の初期値
*/
"defaultScheduledNoteDeleteTime": string;
/**
* ノートの自己消滅が有効になっています
*/
"scheduledNoteDeleteEnabled": string;
/**
* 使用しない場合は空欄にしてください
*/
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
@@ -1337,6 +1337,8 @@ draftSavingBehavior: "下書きの保存に関する動作"
saveAsDraft: "下書きとして保存"
draftOverwriteConfirm: "下書きを適用すると現在入力されている内容はリセットされます。よろしいですか?"
defaultScheduledNoteDelete: "デフォルトでノートが自己消滅するように"
defaultScheduledNoteDeleteTime: "ノートの自己消滅の初期値"
scheduledNoteDeleteEnabled: "ノートの自己消滅が有効になっています"
notUsePleaseLeaveBlank: "使用しない場合は空欄にしてください"
useTotp: "ワンタイムパスワードを使う"
useBackupCode: "バックアップコードを使う"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2024.5.0-kakurega.1.37.0",
"version": "2024.5.0-kakurega.1.38.0",
"codename": "nasubi",
"repository": {
"type": "git",
79 changes: 69 additions & 10 deletions packages/frontend/src/components/MkDeleteScheduleEditor.vue
Original file line number Diff line number Diff line change
@@ -4,10 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<div :class="$style.root">
<span v-if="!afterOnly">{{ i18n.ts.scheduledNoteDelete }}</span>
<div :class="[$style.root, { [$style.padding]: !afterOnly }]">
<div v-if="!afterOnly" :class="[$style.label, { [$style.withAccent]: !showDetail }]" @click="showDetail = !showDetail"><i class="ti" :class="showDetail ? 'ti-chevron-up' : 'ti-chevron-down'"></i> {{ summaryText }}</div>
<MkInfo v-if="!isValid" warn>{{ i18n.ts.cannotScheduleLaterThanOneYear }}</MkInfo>
<section>
<section v-if="afterOnly || showDetail">
<div>
<MkSelect v-if="!afterOnly" v-model="expiration" small>
<template #label>{{ i18n.ts._poll.expiration }}</template>
@@ -39,12 +39,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>

<script lang="ts" setup>
import { ref, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import MkInput from './MkInput.vue';
import MkSelect from './MkSelect.vue';
import MkInfo from './MkInfo.vue';
import { formatDateTimeString } from '@/scripts/format-time-string.js';
import { addTime } from '@/scripts/time.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';

export type DeleteScheduleEditorModelValue = {
@@ -61,21 +62,62 @@ const emit = defineEmits<{
(ev: 'update:modelValue', v: DeleteScheduleEditorModelValue): void;
}>();

const expiration = ref<'at' | 'after'>(props.afterOnly ? 'after' : 'at');
const expiration = ref<'at' | 'after'>('after');
const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'));
const atTime = ref('00:00');
const after = ref(0);
const unit = ref('second');
const unit = ref<'second' | 'minute' | 'hour' | 'day'>('second');
const isValid = ref(true);

const showDetail = ref(!defaultStore.state.defaultScheduledNoteDelete);
const summaryText = computed(() => {
if (showDetail.value) {
return i18n.ts.scheduledNoteDelete;
}

if (expiration.value === 'at') {
return `${i18n.ts.scheduledNoteDeleteEnabled} (${formatDateTimeString(new Date(calcAt()), 'yyyy/MM/dd HH:mm')})`;
} else {
const time = unit.value === 'second' ? i18n.tsx._timeIn.seconds({ n: (after.value).toString() })
: unit.value === 'minute' ? i18n.tsx._timeIn.minutes({ n: (after.value).toString() })
: unit.value === 'hour' ? i18n.tsx._timeIn.hours({ n: (after.value).toString() })
: i18n.tsx._timeIn.days({ n: (after.value).toString() });

return `${i18n.ts.scheduledNoteDeleteEnabled} (${time})`;
}
});

const beautifyAfter = (base: number) => {
let time = base;

if (time % 60 === 0) {
unit.value = 'minute';
time /= 60;
}

if (time % 60 === 0) {
unit.value = 'hour';
time /= 60;
}

if (time % 24 === 0) {
unit.value = 'day';
time /= 24;
}

after.value = time;
};

beautifyAfter(defaultStore.state.defaultScheduledNoteDeleteTime / 1000);

if (props.modelValue.deleteAt) {
expiration.value = 'at';
const deleteAt = new Date(props.modelValue.deleteAt);
atDate.value = formatDateTimeString(deleteAt, 'yyyy-MM-dd');
atTime.value = formatDateTimeString(deleteAt, 'HH:mm');
} else if (typeof props.modelValue.deleteAfter === 'number') {
expiration.value = 'after';
after.value = props.modelValue.deleteAfter / 1000;
beautifyAfter(props.modelValue.deleteAfter / 1000);
}

const calcAt = () => {
@@ -127,8 +169,8 @@ watch([expiration, atDate, atTime, after, unit, isValid], () => {
.root {
display: flex;
flex-direction: column;
gap: 16px;
padding: 8px 16px;
gap: 8px;
padding: 8px 0px;

>span {
opacity: 0.7;
@@ -159,7 +201,6 @@ watch([expiration, atDate, atTime, after, unit, isValid], () => {

>section {
>div {
margin: 0 8px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
@@ -187,4 +228,22 @@ watch([expiration, atDate, atTime, after, unit, isValid], () => {
}
}
}

.padding {
padding: 8px 24px;
}

.label {
font-size: 0.85em;
padding: 0 0 8px 0;
user-select: none;
}

.withAccent {
color: var(--accent);
}

.chevronOpening {
transform: rotateX(180deg);
}
</style>
8 changes: 7 additions & 1 deletion packages/frontend/src/pages/settings/post-form.vue
Original file line number Diff line number Diff line change
@@ -35,11 +35,17 @@
{{ i18n.ts.disableNoteDrafting }}
<span class="_beta">{{ i18n.ts.originalFeature }}</span>
</MkSwitch>
<div>
<div :class="$style.label">
{{ i18n.ts.defaultScheduledNoteDeleteTime }}
<span class="_beta">{{ i18n.ts.originalFeature }}</span>
</div>
<MkDeleteScheduleEditor v-model="scheduledNoteDelete" :afterOnly="true"/>
</div>
<MkSwitch v-model="defaultScheduledNoteDelete">
{{ i18n.ts.defaultScheduledNoteDelete }}
<span class="_beta">{{ i18n.ts.originalFeature }}</span>
</MkSwitch>
<MkDeleteScheduleEditor v-if="defaultScheduledNoteDelete" v-model="scheduledNoteDelete" :afterOnly="true"/>
</div>
</template>

128 changes: 58 additions & 70 deletions packages/frontend/src/scripts/get-user-menu.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,58 @@ import { IRouter } from '@/nirax.js';
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
import { mainRouter } from '@/router/main.js';

type PeriodType = {
key: string;
time: number | null;
text: string;
};

const period: PeriodType[] = [{
key: 'indefinitely',
time: null,
text: i18n.ts.indefinitely,
}, {
key: 'tenMinutes',
time: 1000 * 60 * 10,
text: i18n.tsx._timeIn.minutes({ n: (10).toString() }),
}, {
key: 'oneHour',
time: 1000 * 60 * 60,
text: i18n.tsx._timeIn.hours({ n: (1).toString() }),
}, {
key: 'sixHours',
time: 1000 * 60 * 60 * 6,
text: i18n.tsx._timeIn.hours({ n: (6).toString() }),
}, {
key: 'twelveHours',
time: 1000 * 60 * 60 * 12,
text: i18n.tsx._timeIn.hours({ n: (12).toString() }),
}, {
key: 'oneDay',
time: 1000 * 60 * 60 * 24,
text: i18n.tsx._timeIn.days({ n: (1).toString() }),
}, {
key: 'oneWeek',
time: 1000 * 60 * 60 * 24 * 7,
text: i18n.tsx._timeIn.weeks({ n: (1).toString() }),
}];

async function getPeriod(title: string, text?: string) {
const { canceled, result } = await os.select({
title,
text,
items: period.map(x => ({
value: x.key,
text: x.text,
})),
default: 'indefinitely',
});
if (canceled) return false;

const periodTime = period.find(x => x.key === result)?.time;
return periodTime == null ? null : Date.now() + periodTime;
}

export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) {
const meId = $i ? $i.id : null;

@@ -30,29 +82,8 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
user.isMuted = false;
});
} else {
const { canceled, result: period } = await os.select({
title: i18n.ts.mutePeriod,
items: [{
value: 'indefinitely', text: i18n.ts.indefinitely,
}, {
value: 'tenMinutes', text: i18n.ts.tenMinutes,
}, {
value: 'oneHour', text: i18n.ts.oneHour,
}, {
value: 'oneDay', text: i18n.ts.oneDay,
}, {
value: 'oneWeek', text: i18n.ts.oneWeek,
}],
default: 'indefinitely',
});
if (canceled) return;

const expiresAt = period === 'indefinitely' ? null
: period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10)
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
: null;
const expiresAt = await getPeriod(i18n.ts.mutePeriod);
if (expiresAt === false) return;

os.apiWithDialog('mute/create', {
userId: user.id,
@@ -103,30 +134,8 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
if (user.isMuted) return toggleBlock();
if (user.isBlocking) return toggleMute();

const { canceled, result: period } = await os.select({
title: i18n.ts.muteAndBlockConfirm,
text: i18n.ts.mutePeriod,
items: [{
value: 'indefinitely', text: i18n.ts.indefinitely,
}, {
value: 'tenMinutes', text: i18n.ts.tenMinutes,
}, {
value: 'oneHour', text: i18n.ts.oneHour,
}, {
value: 'oneDay', text: i18n.ts.oneDay,
}, {
value: 'oneWeek', text: i18n.ts.oneWeek,
}],
default: 'indefinitely',
});
if (canceled) return;

const expiresAt = period === 'indefinitely' ? null
: period === 'tenMinutes' ? Date.now() + (1000 * 60 * 10)
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
: null;
const expiresAt = await getPeriod(i18n.ts.muteAndBlockConfirm, i18n.ts.mutePeriod);
if (expiresAt === false) return;

await os.apiWithDialog('mute/create', {
userId: user.id,
@@ -314,29 +323,8 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
return roles.filter(r => r.target === 'manual').map(r => ({
text: r.name,
action: async () => {
const { canceled, result: period } = await os.select({
title: i18n.ts.period + ': ' + r.name,
items: [{
value: 'indefinitely', text: i18n.ts.indefinitely,
}, {
value: 'oneHour', text: i18n.ts.oneHour,
}, {
value: 'oneDay', text: i18n.ts.oneDay,
}, {
value: 'oneWeek', text: i18n.ts.oneWeek,
}, {
value: 'oneMonth', text: i18n.ts.oneMonth,
}],
default: 'indefinitely',
});
if (canceled) return;

const expiresAt = period === 'indefinitely' ? null
: period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
: period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
: period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
: null;
const expiresAt = await getPeriod(i18n.ts.period + ': ' + r.name);
if (expiresAt === false) return;

os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt });
},