Skip to content

Commit 4579be0

Browse files
anatawa12syuilokakkokari-gtyih
authored
新着ノートをサウンドで通知する機能をdeck UIに追加 (#13867)
* feat(deck-ui): implement note notification * chore: remove notify in antenna * docs(changelog): 新着ノートをサウンドで通知する機能をdeck UIに追加 * fix: type error in test * lint: key order * fix: remove notify column * test: remove test for notify * chore: make sound selectable * fix: add license header * fix: add license header again * Unnecessary await Co-authored-by: かっこかり <[email protected]> * ファイルを選択してください -> ファイルが選択されていません * fix: i18n忘れ * fix: i18n忘れ * pleaseSelectFile > fileNotSelected --------- Co-authored-by: syuilo <[email protected]> Co-authored-by: かっこかり <[email protected]>
1 parent d7982e4 commit 4579be0

26 files changed

+341
-53
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
- Enhance: AiScriptを0.18.0にバージョンアップ
5050
- Enhance: 通常のノートでも、お気に入りに登録したチャンネルにリノートできるように
5151
- Enhance: 長いテキストをペーストした際にテキストファイルとして添付するかどうかを選択できるように
52+
- Enhance: 新着ノートをサウンドで通知する機能をdeck UIに追加しました
5253
- Enhance: コントロールパネルのクイックアクションからファイルを照会できるように
5354
- Enhance: コントロールパネルのクイックアクションから通常の照会を行えるように
5455
- Fix: 一部のページ内リンクが正しく動作しない問題を修正

locales/index.d.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1280,6 +1280,10 @@ export interface Locale extends ILocale {
12801280
* フォルダーを選択
12811281
*/
12821282
"selectFolders": string;
1283+
/**
1284+
* ファイルが選択されていません
1285+
*/
1286+
"fileNotSelected": string;
12831287
/**
12841288
* ファイル名を変更
12851289
*/
@@ -9143,6 +9147,10 @@ export interface Locale extends ILocale {
91439147
* カラムを追加
91449148
*/
91459149
"addColumn": string;
9150+
/**
9151+
* 新着ノート通知の設定
9152+
*/
9153+
"newNoteNotificationSettings": string;
91469154
/**
91479155
* カラムの設定
91489156
*/

locales/ja-JP.yml

+2
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ selectFile: "ファイルを選択"
316316
selectFiles: "ファイルを選択"
317317
selectFolder: "フォルダーを選択"
318318
selectFolders: "フォルダーを選択"
319+
fileNotSelected: "ファイルが選択されていません"
319320
renameFile: "ファイル名を変更"
320321
folderName: "フォルダー名"
321322
createFolder: "フォルダーを作成"
@@ -2420,6 +2421,7 @@ _deck:
24202421
alwaysShowMainColumn: "常にメインカラムを表示"
24212422
columnAlign: "カラムの寄せ"
24222423
addColumn: "カラムを追加"
2424+
newNoteNotificationSettings: "新着ノート通知の設定"
24232425
configureColumn: "カラムの設定"
24242426
swapLeft: "左に移動"
24252427
swapRight: "右に移動"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* SPDX-FileCopyrightText: syuilo and misskey-project
3+
* SPDX-License-Identifier: AGPL-3.0-only
4+
*/
5+
6+
export class RemoveAntennaNotify1716450883149 {
7+
name = 'RemoveAntennaNotify1716450883149'
8+
9+
async up(queryRunner) {
10+
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "notify"`);
11+
}
12+
13+
async down(queryRunner) {
14+
await queryRunner.query(`ALTER TABLE "antenna" ADD "notify" boolean NOT NULL`);
15+
}
16+
}

packages/backend/src/core/entities/AntennaEntityService.ts

-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export class AntennaEntityService {
3838
users: antenna.users,
3939
caseSensitive: antenna.caseSensitive,
4040
localOnly: antenna.localOnly,
41-
notify: antenna.notify,
4241
excludeBots: antenna.excludeBots,
4342
withReplies: antenna.withReplies,
4443
withFile: antenna.withFile,

packages/backend/src/models/Antenna.ts

-3
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,6 @@ export class MiAntenna {
9090
})
9191
public expression: string | null;
9292

93-
@Column('boolean')
94-
public notify: boolean;
95-
9693
@Index()
9794
@Column('boolean', {
9895
default: true,

packages/backend/src/models/json-schema/antenna.ts

-4
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@ export const packedAntennaSchema = {
7272
optional: false, nullable: false,
7373
default: false,
7474
},
75-
notify: {
76-
type: 'boolean',
77-
optional: false, nullable: false,
78-
},
7975
excludeBots: {
8076
type: 'boolean',
8177
optional: false, nullable: false,

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

-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ export class ExportAntennasProcessorService {
8484
excludeBots: antenna.excludeBots,
8585
withReplies: antenna.withReplies,
8686
withFile: antenna.withFile,
87-
notify: antenna.notify,
8887
}));
8988
if (antennas.length - 1 !== index) {
9089
write(', ');

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ const validate = new Ajv().compile({
4747
excludeBots: { type: 'boolean' },
4848
withReplies: { type: 'boolean' },
4949
withFile: { type: 'boolean' },
50-
notify: { type: 'boolean' },
5150
},
52-
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'],
51+
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'],
5352
});
5453

5554
@Injectable()
@@ -92,7 +91,6 @@ export class ImportAntennasProcessorService {
9291
excludeBots: antenna.excludeBots,
9392
withReplies: antenna.withReplies,
9493
withFile: antenna.withFile,
95-
notify: antenna.notify,
9694
}).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0]));
9795
this.logger.succ('Antenna created: ' + result.id);
9896
this.globalEventService.publishInternalEvent('antennaCreated', result);

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,8 @@ export const paramDef = {
6767
excludeBots: { type: 'boolean' },
6868
withReplies: { type: 'boolean' },
6969
withFile: { type: 'boolean' },
70-
notify: { type: 'boolean' },
7170
},
72-
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'],
71+
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'],
7372
} as const;
7473

7574
@Injectable()
@@ -128,7 +127,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
128127
excludeBots: ps.excludeBots,
129128
withReplies: ps.withReplies,
130129
withFile: ps.withFile,
131-
notify: ps.notify,
132130
}).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0]));
133131

134132
this.globalEventService.publishInternalEvent('antennaCreated', antenna);

packages/backend/src/server/api/endpoints/antennas/update.ts

-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export const paramDef = {
6666
excludeBots: { type: 'boolean' },
6767
withReplies: { type: 'boolean' },
6868
withFile: { type: 'boolean' },
69-
notify: { type: 'boolean' },
7069
},
7170
required: ['antennaId'],
7271
} as const;
@@ -124,7 +123,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
124123
excludeBots: ps.excludeBots,
125124
withReplies: ps.withReplies,
126125
withFile: ps.withFile,
127-
notify: ps.notify,
128126
isActive: true,
129127
lastUsedAt: new Date(),
130128
});

packages/backend/test/e2e/antennas.ts

-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ describe('アンテナ', () => {
3838
excludeKeywords: [['']],
3939
keywords: [['keyword']],
4040
name: 'test',
41-
notify: false,
4241
src: 'all' as const,
4342
userListId: null,
4443
users: [''],
@@ -151,7 +150,6 @@ describe('アンテナ', () => {
151150
isActive: true,
152151
keywords: [['keyword']],
153152
name: 'test',
154-
notify: false,
155153
src: 'all',
156154
userListId: null,
157155
users: [''],
@@ -219,8 +217,6 @@ describe('アンテナ', () => {
219217
{ parameters: () => ({ withReplies: true }) },
220218
{ parameters: () => ({ withFile: false }) },
221219
{ parameters: () => ({ withFile: true }) },
222-
{ parameters: () => ({ notify: false }) },
223-
{ parameters: () => ({ notify: true }) },
224220
];
225221
test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => {
226222
const response = await successfulApiCall({

packages/backend/test/e2e/move.ts

-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ describe('Account Move', () => {
191191
localOnly: false,
192192
withReplies: false,
193193
withFile: false,
194-
notify: false,
195194
}, alice);
196195
antennaId = antenna.body.id;
197196

@@ -435,7 +434,6 @@ describe('Account Move', () => {
435434
localOnly: false,
436435
withReplies: false,
437436
withFile: false,
438-
notify: false,
439437
}, alice);
440438

441439
assert.strictEqual(res.status, 403);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!--
2+
SPDX-FileCopyrightText: syuilo and misskey-project
3+
SPDX-License-Identifier: AGPL-3.0-only
4+
-->
5+
6+
<template>
7+
<div>
8+
<MkButton inline rounded primary @click="selectButton($event)">{{ i18n.ts.selectFile }}</MkButton>
9+
<div :class="['_nowrap', !fileName && $style.fileNotSelected]">{{ friendlyFileName }}</div>
10+
</div>
11+
</template>
12+
13+
<script setup lang="ts">
14+
import * as Misskey from 'misskey-js';
15+
import { computed, ref } from 'vue';
16+
import { i18n } from '@/i18n.js';
17+
import MkButton from '@/components/MkButton.vue';
18+
import { selectFile } from '@/scripts/select-file.js';
19+
import { misskeyApi } from '@/scripts/misskey-api.js';
20+
21+
const props = defineProps<{
22+
fileId?: string | null;
23+
validate?: (file: Misskey.entities.DriveFile) => Promise<boolean>;
24+
}>();
25+
26+
const emit = defineEmits<{
27+
(ev: 'update', result: Misskey.entities.DriveFile): void;
28+
}>();
29+
30+
const fileUrl = ref('');
31+
const fileName = ref<string>('');
32+
33+
const friendlyFileName = computed<string>(() => {
34+
if (fileName.value) {
35+
return fileName.value;
36+
}
37+
if (fileUrl.value) {
38+
return fileUrl.value;
39+
}
40+
41+
return i18n.ts.fileNotSelected;
42+
});
43+
44+
if (props.fileId) {
45+
misskeyApi('drive/files/show', {
46+
fileId: props.fileId,
47+
}).then((apiRes) => {
48+
fileName.value = apiRes.name;
49+
fileUrl.value = apiRes.url;
50+
});
51+
}
52+
53+
function selectButton(ev: MouseEvent) {
54+
selectFile(ev.currentTarget ?? ev.target).then(async (file) => {
55+
if (!file) return;
56+
if (props.validate && !await props.validate(file)) return;
57+
58+
emit('update', file);
59+
fileName.value = file.name;
60+
fileUrl.value = file.url;
61+
});
62+
}
63+
64+
</script>
65+
66+
<style module>
67+
.fileNotSelected {
68+
font-weight: 700;
69+
color: var(--infoWarnFg);
70+
}
71+
</style>

packages/frontend/src/components/MkFormDialog.vue

+10-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
2121

2222
<MkSpacer :marginMin="20" :marginMax="32">
2323
<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
24-
<template v-for="(v, k) in Object.fromEntries(Object.entries(form).filter(([_, v]) => !('hidden' in v) || 'hidden' in v && !v.hidden))">
25-
<MkInput v-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1">
24+
<template v-for="(v, k) in Object.fromEntries(Object.entries(form))">
25+
<template v-if="typeof v.hidden == 'function' ? v.hidden(values) : v.hidden"></template>
26+
<MkInput v-else-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1">
2627
<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
2728
<template v-if="v.description" #caption>{{ v.description }}</template>
2829
</MkInput>
@@ -53,6 +54,12 @@ SPDX-License-Identifier: AGPL-3.0-only
5354
<MkButton v-else-if="v.type === 'button'" @click="v.action($event, values)">
5455
<span v-text="v.content || k"></span>
5556
</MkButton>
57+
<XFile
58+
v-else-if="v.type === 'drive-file'"
59+
:fileId="v.defaultFileId"
60+
:validate="async f => !v.validate || await v.validate(f)"
61+
@update="f => values[k] = f"
62+
/>
5663
</template>
5764
</div>
5865
<div v-else class="_fullinfo">
@@ -72,6 +79,7 @@ import MkSelect from './MkSelect.vue';
7279
import MkRange from './MkRange.vue';
7380
import MkButton from './MkButton.vue';
7481
import MkRadios from './MkRadios.vue';
82+
import XFile from './MkFormDialog.file.vue';
7583
import type { Form } from '@/scripts/form.js';
7684
import MkModalWindow from '@/components/MkModalWindow.vue';
7785
import { i18n } from '@/i18n.js';

packages/frontend/src/os.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ export function waiting(): Promise<void> {
518518
});
519519
}
520520

521-
export function form<F extends Form>(title: string, f: F): Promise<{ canceled: true } | { result: GetFormResultType<F> }> {
521+
export function form<F extends Form>(title: string, f: F): Promise<{ canceled: true, result?: undefined } | { canceled?: false, result: GetFormResultType<F> }> {
522522
return new Promise(resolve => {
523523
popup(defineAsyncComponent(() => import('@/components/MkFormDialog.vue')), { title, form: f }, {
524524
done: result => {

packages/frontend/src/pages/my-antennas/editor.vue

-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ SPDX-License-Identifier: AGPL-3.0-only
3939
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
4040
<MkSwitch v-model="caseSensitive">{{ i18n.ts.caseSensitive }}</MkSwitch>
4141
<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch>
42-
<MkSwitch v-model="notify">{{ i18n.ts.notifyAntenna }}</MkSwitch>
4342
</div>
4443
<div :class="$style.actions">
4544
<MkButton inline primary @click="saveAntenna()"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
@@ -82,7 +81,6 @@ const localOnly = ref<boolean>(props.antenna.localOnly);
8281
const excludeBots = ref<boolean>(props.antenna.excludeBots);
8382
const withReplies = ref<boolean>(props.antenna.withReplies);
8483
const withFile = ref<boolean>(props.antenna.withFile);
85-
const notify = ref<boolean>(props.antenna.notify);
8684
const userLists = ref<Misskey.entities.UserList[] | null>(null);
8785

8886
watch(() => src.value, async () => {
@@ -99,7 +97,6 @@ async function saveAntenna() {
9997
excludeBots: excludeBots.value,
10098
withReplies: withReplies.value,
10199
withFile: withFile.value,
102-
notify: notify.value,
103100
caseSensitive: caseSensitive.value,
104101
localOnly: localOnly.value,
105102
users: users.value.trim().split('\n').map(x => x.trim()),

0 commit comments

Comments
 (0)