Skip to content

Commit fbbcbaa

Browse files
committed
Merge tag '2024.5.0-io.6' into bun
2 parents cbfa013 + f280ce2 commit fbbcbaa

24 files changed

+441
-106
lines changed

locales/en-US.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,8 @@ consentAll: "Allow All Items"
13171317
consentSelected: "Allow Selected Items"
13181318
emailAddressLogin: "Login with email address"
13191319
usernameLogin: "Login with username"
1320+
autoloadDrafts: "Automatically load drafts when opening the posting form"
1321+
drafts: "Drafts"
13201322

13211323
_bubbleGame:
13221324
howToPlay: "How to play"

locales/index.d.ts

+8
Original file line numberDiff line numberDiff line change
@@ -5330,6 +5330,14 @@ export interface Locale extends ILocale {
53305330
* ユーザー名でログイン
53315331
*/
53325332
"usernameLogin": string;
5333+
/**
5334+
* 投稿フォームを開いたときに下書きを自動で読み込む
5335+
*/
5336+
"autoloadDrafts": string;
5337+
/**
5338+
* 下書き
5339+
*/
5340+
"drafts": string;
53335341
"_bubbleGame": {
53345342
/**
53355343
* 遊び方

locales/ja-JP.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,8 @@ consentAll: "全て許可"
13261326
consentSelected: "選択した項目のみ許可"
13271327
emailAddressLogin: "メールアドレスでログイン"
13281328
usernameLogin: "ユーザー名でログイン"
1329+
autoloadDrafts: "投稿フォームを開いたときに下書きを自動で読み込む"
1330+
drafts: "下書き"
13291331

13301332
_bubbleGame:
13311333
howToPlay: "遊び方"

locales/ko-KR.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,8 @@ consentAll: "모두 허용"
13141314
consentSelected: "선택한 항목만 허용"
13151315
emailAddressLogin: "이메일 주소로 로그인"
13161316
usernameLogin: "사용자명으로 로그인"
1317+
autoloadDrafts: "글 작성 시 자동으로 임시 저장된 글 불러오기"
1318+
drafts: "임시 저장"
13171319

13181320
_bubbleGame:
13191321
howToPlay: "설명"

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "misskey",
3-
"version": "2024.5.0-io.5e",
3+
"version": "2024.5.0-io.6",
44
"codename": "nasubi",
55
"repository": {
66
"type": "git",

packages/backend/src/core/chart/ChartManagementService.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ export class ChartManagementService implements OnApplicationShutdown {
5858
@bindThis
5959
public async start() {
6060
// 20分おきにメモリ情報をDBに書き込み
61-
this.saveIntervalId = setInterval(() => {
61+
this.saveIntervalId = setInterval(async () => {
6262
for (const chart of this.charts) {
63-
chart.save();
63+
await chart.save();
6464
}
6565
}, 1000 * 60 * 20);
6666
}
@@ -69,9 +69,9 @@ export class ChartManagementService implements OnApplicationShutdown {
6969
public async dispose(): Promise<void> {
7070
clearInterval(this.saveIntervalId);
7171
if (process.env.NODE_ENV !== 'test') {
72-
await Promise.all(
73-
this.charts.map(chart => chart.save()),
74-
);
72+
for (const chart of this.charts) {
73+
await chart.save();
74+
}
7575
}
7676
}
7777

packages/backend/src/queue/processors/CleanChartsProcessorService.ts

+12-14
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,18 @@ export class CleanChartsProcessorService {
4848
public async process(): Promise<void> {
4949
this.logger.info('Clean charts...');
5050

51-
await Promise.all([
52-
this.federationChart.clean(),
53-
this.notesChart.clean(),
54-
this.usersChart.clean(),
55-
this.activeUsersChart.clean(),
56-
this.instanceChart.clean(),
57-
this.perUserNotesChart.clean(),
58-
this.perUserPvChart.clean(),
59-
this.driveChart.clean(),
60-
this.perUserReactionsChart.clean(),
61-
this.perUserFollowingChart.clean(),
62-
this.perUserDriveChart.clean(),
63-
this.apRequestChart.clean(),
64-
]);
51+
await this.federationChart.clean();
52+
await this.notesChart.clean();
53+
await this.usersChart.clean();
54+
await this.activeUsersChart.clean();
55+
await this.instanceChart.clean();
56+
await this.perUserNotesChart.clean();
57+
await this.perUserPvChart.clean();
58+
await this.driveChart.clean();
59+
await this.perUserReactionsChart.clean();
60+
await this.perUserFollowingChart.clean();
61+
await this.perUserDriveChart.clean();
62+
await this.apRequestChart.clean();
6563

6664
this.logger.succ('All charts successfully cleaned.');
6765
}

packages/backend/src/queue/processors/ResyncChartsProcessorService.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ export class ResyncChartsProcessorService {
3131

3232
// TODO: ユーザーごとのチャートも更新する
3333
// TODO: インスタンスごとのチャートも更新する
34-
await Promise.all([
35-
this.driveChart.resync(),
36-
this.notesChart.resync(),
37-
this.usersChart.resync(),
38-
]);
34+
await this.driveChart.resync();
35+
await this.notesChart.resync();
36+
await this.usersChart.resync();
3937

4038
this.logger.succ('All charts successfully resynced.');
4139
}

packages/backend/src/queue/processors/TickChartsProcessorService.ts

+12-14
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,18 @@ export class TickChartsProcessorService {
4848
public async process(): Promise<void> {
4949
this.logger.info('Tick charts...');
5050

51-
await Promise.all([
52-
this.federationChart.tick(false),
53-
this.notesChart.tick(false),
54-
this.usersChart.tick(false),
55-
this.activeUsersChart.tick(false),
56-
this.instanceChart.tick(false),
57-
this.perUserNotesChart.tick(false),
58-
this.perUserPvChart.tick(false),
59-
this.driveChart.tick(false),
60-
this.perUserReactionsChart.tick(false),
61-
this.perUserFollowingChart.tick(false),
62-
this.perUserDriveChart.tick(false),
63-
this.apRequestChart.tick(false),
64-
]);
51+
await this.federationChart.tick(false);
52+
await this.notesChart.tick(false);
53+
await this.usersChart.tick(false);
54+
await this.activeUsersChart.tick(false);
55+
await this.instanceChart.tick(false);
56+
await this.perUserNotesChart.tick(false);
57+
await this.perUserPvChart.tick(false);
58+
await this.driveChart.tick(false);
59+
await this.perUserReactionsChart.tick(false);
60+
await this.perUserFollowingChart.tick(false);
61+
await this.perUserDriveChart.tick(false);
62+
await this.apRequestChart.tick(false);
6563

6664
this.logger.succ('All charts successfully ticked.');
6765
}

packages/backend/src/server/api/endpoints/notes/create.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const meta = {
4040

4141
res: {
4242
type: 'object',
43-
optional: false, nullable: false,
43+
optional: true, nullable: false,
4444
properties: {
4545
createdNote: {
4646
type: 'object',
@@ -207,6 +207,7 @@ export const paramDef = {
207207
},
208208
required: ['choices'],
209209
},
210+
noCreatedNote: { type: 'boolean', default: false },
210211
},
211212
// (re)note with text, files and poll are optional
212213
if: {
@@ -281,7 +282,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
281282
const note = await this.notesRepository.findOneBy({ id: idempotent });
282283
if (note) {
283284
logger.info('The request has already been processed.', { noteId: note.id });
284-
return { createdNote: await this.noteEntityService.pack(note, me) };
285+
if (ps.noCreatedNote) return;
286+
else return { createdNote: await this.noteEntityService.pack(note, me) };
285287
}
286288
}
287289

@@ -453,7 +455,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
453455
await this.redisForTimelines.set(`note:idempotent:${me.id}:${hash}`, note.id, 'EX', 60);
454456

455457
logger.info('Successfully created a note.', { noteId: note.id });
456-
return {
458+
if (ps.noCreatedNote) return;
459+
else return {
457460
createdNote: await this.noteEntityService.pack(note, me),
458461
};
459462
} catch (err) {

packages/backend/src/server/web/cli.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ window.onload = async () => {
4141

4242
document.getElementById('submit').addEventListener('click', () => {
4343
api('notes/create', {
44-
text: document.getElementById('text').value
44+
text: document.getElementById('text').value,
45+
noCreatedNote: true,
4546
}).then(() => {
4647
location.reload();
4748
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<template>
2+
<MkModalWindow
3+
ref="dialog"
4+
:height="500"
5+
:width="800"
6+
@click="done(true)"
7+
@close="done(true)"
8+
@closed="emit('closed')"
9+
>
10+
<template #header>
11+
{{ i18n.ts.drafts }}
12+
</template>
13+
<div style="display: flex; flex-direction: column">
14+
<div v-if="drafts.length === 0" class="empty">
15+
<div class="_fullinfo">
16+
<img :src="infoImageUrl" class="_ghost"/>
17+
<div>{{ i18n.ts.nothing }}</div>
18+
</div>
19+
</div>
20+
<div v-for="draft in drafts" :key="draft.id" :class="$style.draftItem">
21+
<div :class="$style.draftNote" @click="selectDraft(draft.id)">
22+
<div :class="$style.draftNoteHeader">
23+
<div :class="$style.draftNoteDestination">
24+
<span v-if="draft.channel" style="opacity: 0.7; padding-right: 0.5em">
25+
<i class="ti ti-device-tv"></i> {{ draft.channel.name }}
26+
</span>
27+
<span v-if="draft.renote">
28+
<i class="ti ti-quote"></i> <MkAcct :user="draft.renote.user" /> <span>{{ draft.renote.text }}</span>
29+
</span>
30+
<span v-else-if="draft.reply">
31+
<i class="ti ti-arrow-back-up"></i> <MkAcct :user="draft.reply.user" /> <span>{{ draft.reply.text }}</span>
32+
</span>
33+
<span v-else>
34+
<i class="ti ti-pencil"></i>
35+
</span>
36+
</div>
37+
<div :class="$style.draftNoteInfo">
38+
<MkTime :time="draft.createdAt" colored />
39+
<span v-if="draft.visibility !== 'public'" :title="i18n.ts._visibility[draft.visibility]" style="margin-left: 0.5em">
40+
<i v-if="draft.visibility === 'home'" class="ti ti-home"></i>
41+
<i v-else-if="draft.visibility === 'followers'" class="ti ti-lock"></i>
42+
<i v-else-if="draft.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
43+
</span>
44+
<span v-if="draft.localOnly" :title="i18n.ts._visibility['disableFederation']" style="margin-left: 0.5em">
45+
<i class="ti ti-rocket-off"></i>
46+
</span>
47+
<span v-if="draft.channel" :title="draft.channel.name" style="margin-left: 0.5em">
48+
<i class="ti ti-device-tv"></i>
49+
</span>
50+
</div>
51+
</div>
52+
<div>
53+
<p v-if="!!draft.cw" :class="$style.draftNoteCw">
54+
<Mfm :text="draft.cw" />
55+
</p>
56+
<MkSubNoteContent :class="$style.draftNoteText" :note="draft" />
57+
</div>
58+
</div>
59+
<button :class="$style.delete" class="_button" @click="removeDraft(draft.id)">
60+
<i class="ti ti-trash"></i>
61+
</button>
62+
</div>
63+
</div>
64+
</MkModalWindow>
65+
</template>
66+
67+
<script lang="ts" setup>
68+
import { onActivated, onMounted, ref, shallowRef } from 'vue';
69+
import * as Misskey from 'misskey-js';
70+
import { miLocalStorage } from '@/local-storage.js';
71+
import { infoImageUrl } from '@/instance.js';
72+
import { i18n } from '@/i18n.js';
73+
import { $i } from '@/account.js';
74+
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
75+
import MkModalWindow from '@/components/MkModalWindow.vue';
76+
import type { NoteDraftItem } from '@/types/note-draft-item.js';
77+
78+
const emit = defineEmits<{
79+
(ev: 'done', v: { canceled: true } | { canceled: false; selected: string | undefined }): void;
80+
(ev: 'closed'): void;
81+
}>();
82+
83+
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
84+
85+
const drafts = ref<(Misskey.entities.Note & { useCw: boolean })[]>([]);
86+
87+
onMounted(loadDrafts);
88+
onActivated(loadDrafts);
89+
90+
function loadDrafts() {
91+
const stored = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}') as Record<string, NoteDraftItem>;
92+
drafts.value = Object.keys(stored).map((key) => ({
93+
...(stored[key].data as Misskey.entities.Note & { useCw: boolean }),
94+
id: key,
95+
createdAt: stored[key].updatedAt,
96+
channel: stored[key].channel as Misskey.entities.Channel,
97+
renote: stored[key].renote as Misskey.entities.Note,
98+
reply: stored[key].reply as Misskey.entities.Note,
99+
user: $i as Misskey.entities.User,
100+
}));
101+
}
102+
103+
function selectDraft(draft: string) {
104+
done(false, draft);
105+
}
106+
107+
function removeDraft(draft: string) {
108+
const stored = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}') as Record<string, NoteDraftItem>;
109+
110+
delete stored[draft];
111+
miLocalStorage.setItem('drafts', JSON.stringify(stored));
112+
113+
loadDrafts();
114+
}
115+
116+
function done(canceled: boolean, selected?: string): void {
117+
emit('done', { canceled, selected } as
118+
| { canceled: true }
119+
| { canceled: false; selected: string | undefined });
120+
dialog.value?.close();
121+
}
122+
</script>
123+
124+
<style lang="scss" module>
125+
.draftItem {
126+
display: flex;
127+
padding: 8px 0 8px 0;
128+
border-bottom: 1px solid var(--divider);
129+
130+
&:hover {
131+
color: var(--accent);
132+
background-color: var(--accentedBg);
133+
}
134+
}
135+
136+
.draftNote {
137+
flex: 1;
138+
width: calc(100% - 16px - 48px - 4px);
139+
margin: 0 8px 0 8px;
140+
}
141+
142+
.draftNoteHeader {
143+
display: flex;
144+
flex-wrap: nowrap;
145+
margin-bottom: 4px;
146+
}
147+
148+
.draftNoteDestination {
149+
flex-shrink: 1;
150+
flex-grow: 1;
151+
overflow: hidden;
152+
text-overflow: ellipsis;
153+
white-space: nowrap;
154+
margin-right: 4px;
155+
}
156+
157+
.draftNoteInfo {
158+
flex-shrink: 0;
159+
margin-left: auto;
160+
}
161+
162+
.draftNoteCw {
163+
cursor: default;
164+
display: block;
165+
overflow-wrap: break-word;
166+
}
167+
168+
.draftNoteText {
169+
cursor: default;
170+
}
171+
172+
.delete {
173+
width: 48px;
174+
height: 64px;
175+
display: flex;
176+
align-self: center;
177+
justify-content: center;
178+
align-items: center;
179+
background-color: var(--buttonBg);
180+
border-radius: 4px;
181+
margin-right: 4px;
182+
}
183+
</style>

0 commit comments

Comments
 (0)