enhance: ロールにアサインされたときの通知 (#12607)
* wip * Update misskey-js.api.md * Update CHANGELOG.md * Update RoleService.ts * Update locales/ja-JP.yml Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> * Update UserListService.ts * Update misskey-js.api.md * fix (#12724) --------- Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Co-authored-by: おさむのひと <46447427+samunohito@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									d14eb20122
								
							
						
					
					
						commit
						15b0d2aff2
					
				
					 15 changed files with 143 additions and 22 deletions
				
			
		| 
						 | 
				
			
			@ -31,6 +31,7 @@
 | 
			
		|||
- Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed)
 | 
			
		||||
- Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83)
 | 
			
		||||
- Feat: TL上からノートが見えなくなるワードミュートであるハードミュートを追加
 | 
			
		||||
- Enhance: 公開ロールにアサインされたときに通知が作成されるように
 | 
			
		||||
- Enhance: アイコンデコレーションを複数設定できるように
 | 
			
		||||
- Enhance: アイコンデコレーションの位置を微調整できるように
 | 
			
		||||
- Enhance: つながりの公開範囲をフォロー/フォロワーで個別に設定可能に #12072
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -2325,6 +2325,7 @@ export interface Locale {
 | 
			
		|||
        "pollEnded": string;
 | 
			
		||||
        "newNote": string;
 | 
			
		||||
        "unreadAntennaNote": string;
 | 
			
		||||
        "roleAssigned": string;
 | 
			
		||||
        "emptyPushNotificationMessage": string;
 | 
			
		||||
        "achievementEarned": string;
 | 
			
		||||
        "testNotification": string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2227,6 +2227,7 @@ _notification:
 | 
			
		|||
  pollEnded: "アンケートの結果が出ました"
 | 
			
		||||
  newNote: "新しい投稿"
 | 
			
		||||
  unreadAntennaNote: "アンテナ {name}"
 | 
			
		||||
  roleAssigned: "ロールが付与されました"
 | 
			
		||||
  emptyPushNotificationMessage: "プッシュ通知の更新をしました"
 | 
			
		||||
  achievementEarned: "実績を獲得"
 | 
			
		||||
  testNotification: "通知テスト"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,14 @@
 | 
			
		|||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import * as Redis from 'ioredis';
 | 
			
		||||
import { In } from 'typeorm';
 | 
			
		||||
import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
 | 
			
		||||
import { ModuleRef } from '@nestjs/core';
 | 
			
		||||
import type {
 | 
			
		||||
	MiRole,
 | 
			
		||||
	MiRoleAssignment,
 | 
			
		||||
	RoleAssignmentsRepository,
 | 
			
		||||
	RolesRepository,
 | 
			
		||||
	UsersRepository,
 | 
			
		||||
} from '@/models/_.js';
 | 
			
		||||
import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
 | 
			
		||||
import type { MiUser } from '@/models/User.js';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -16,12 +23,13 @@ import { CacheService } from '@/core/CacheService.js';
 | 
			
		|||
import type { RoleCondFormulaValue } from '@/models/Role.js';
 | 
			
		||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
 | 
			
		||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
 | 
			
		||||
import { IdService } from '@/core/IdService.js';
 | 
			
		||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
 | 
			
		||||
import { IdService } from '@/core/IdService.js';
 | 
			
		||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
 | 
			
		||||
import type { Packed } from '@/misc/json-schema.js';
 | 
			
		||||
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
 | 
			
		||||
import type { OnApplicationShutdown } from '@nestjs/common';
 | 
			
		||||
import { NotificationService } from '@/core/NotificationService.js';
 | 
			
		||||
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
export type RolePolicies = {
 | 
			
		||||
	gtlAvailable: boolean;
 | 
			
		||||
| 
						 | 
				
			
			@ -78,14 +86,17 @@ export const DEFAULT_POLICIES: RolePolicies = {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class RoleService implements OnApplicationShutdown {
 | 
			
		||||
export class RoleService implements OnApplicationShutdown, OnModuleInit {
 | 
			
		||||
	private rolesCache: MemorySingleCache<MiRole[]>;
 | 
			
		||||
	private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
 | 
			
		||||
	private notificationService: NotificationService;
 | 
			
		||||
 | 
			
		||||
	public static AlreadyAssignedError = class extends Error {};
 | 
			
		||||
	public static NotAssignedError = class extends Error {};
 | 
			
		||||
 | 
			
		||||
	constructor(
 | 
			
		||||
		private moduleRef: ModuleRef,
 | 
			
		||||
 | 
			
		||||
		@Inject(DI.redis)
 | 
			
		||||
		private redisClient: Redis.Redis,
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,6 +131,10 @@ export class RoleService implements OnApplicationShutdown {
 | 
			
		|||
		this.redisForSub.on('message', this.onMessage);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async onModuleInit() {
 | 
			
		||||
		this.notificationService = this.moduleRef.get(NotificationService.name);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	private async onMessage(_: string, data: string): Promise<void> {
 | 
			
		||||
		const obj = JSON.parse(data);
 | 
			
		||||
| 
						 | 
				
			
			@ -427,6 +442,12 @@ export class RoleService implements OnApplicationShutdown {
 | 
			
		|||
 | 
			
		||||
		this.globalEventService.publishInternalEvent('userRoleAssigned', created);
 | 
			
		||||
 | 
			
		||||
		if (role.isPublic) {
 | 
			
		||||
			this.notificationService.createNotification(userId, 'roleAssigned', {
 | 
			
		||||
				roleId: roleId,
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (moderator) {
 | 
			
		||||
			const user = await this.usersRepository.findOneByOrFail({ id: userId });
 | 
			
		||||
			this.moderationLogService.log(moderator, 'assignRole', {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,15 +10,15 @@ import type { MiUser } from '@/models/User.js';
 | 
			
		|||
import type { MiUserList } from '@/models/UserList.js';
 | 
			
		||||
import type { MiUserListMembership } from '@/models/UserListMembership.js';
 | 
			
		||||
import { IdService } from '@/core/IdService.js';
 | 
			
		||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
 | 
			
		||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
 | 
			
		||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
 | 
			
		||||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import { RoleService } from '@/core/RoleService.js';
 | 
			
		||||
import { QueueService } from '@/core/QueueService.js';
 | 
			
		||||
import { RedisKVCache } from '@/misc/cache.js';
 | 
			
		||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
 | 
			
		||||
import { RoleService } from '@/core/RoleService.js';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class UserListService implements OnApplicationShutdown {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,8 +15,8 @@ import type { Packed } from '@/misc/json-schema.js';
 | 
			
		|||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import { isNotNull } from '@/misc/is-not-null.js';
 | 
			
		||||
import { FilterUnionByProperty, notificationTypes } from '@/types.js';
 | 
			
		||||
import { RoleEntityService } from './RoleEntityService.js';
 | 
			
		||||
import type { OnModuleInit } from '@nestjs/common';
 | 
			
		||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
 | 
			
		||||
import type { UserEntityService } from './UserEntityService.js';
 | 
			
		||||
import type { NoteEntityService } from './NoteEntityService.js';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ const NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES = new Set(['note', 'mention', 're
 | 
			
		|||
export class NotificationEntityService implements OnModuleInit {
 | 
			
		||||
	private userEntityService: UserEntityService;
 | 
			
		||||
	private noteEntityService: NoteEntityService;
 | 
			
		||||
	private customEmojiService: CustomEmojiService;
 | 
			
		||||
	private roleEntityService: RoleEntityService;
 | 
			
		||||
 | 
			
		||||
	constructor(
 | 
			
		||||
		private moduleRef: ModuleRef,
 | 
			
		||||
| 
						 | 
				
			
			@ -43,14 +43,13 @@ export class NotificationEntityService implements OnModuleInit {
 | 
			
		|||
 | 
			
		||||
		//private userEntityService: UserEntityService,
 | 
			
		||||
		//private noteEntityService: NoteEntityService,
 | 
			
		||||
		//private customEmojiService: CustomEmojiService,
 | 
			
		||||
	) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onModuleInit() {
 | 
			
		||||
		this.userEntityService = this.moduleRef.get('UserEntityService');
 | 
			
		||||
		this.noteEntityService = this.moduleRef.get('NoteEntityService');
 | 
			
		||||
		this.customEmojiService = this.moduleRef.get('CustomEmojiService');
 | 
			
		||||
		this.roleEntityService = this.moduleRef.get('RoleEntityService');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +80,7 @@ export class NotificationEntityService implements OnModuleInit {
 | 
			
		|||
					detail: false,
 | 
			
		||||
				})
 | 
			
		||||
		) : undefined;
 | 
			
		||||
		const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
 | 
			
		||||
 | 
			
		||||
		return await awaitAll({
 | 
			
		||||
			id: notification.id,
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +92,9 @@ export class NotificationEntityService implements OnModuleInit {
 | 
			
		|||
			...(notification.type === 'reaction' ? {
 | 
			
		||||
				reaction: notification.reaction,
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(notification.type === 'roleAssigned' ? {
 | 
			
		||||
				role: role,
 | 
			
		||||
			} : {}),
 | 
			
		||||
			...(notification.type === 'achievementEarned' ? {
 | 
			
		||||
				achievement: notification.achievement,
 | 
			
		||||
			} : {}),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,11 +3,10 @@
 | 
			
		|||
 * SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { notificationTypes } from '@/types.js';
 | 
			
		||||
import { MiUser } from './User.js';
 | 
			
		||||
import { MiNote } from './Note.js';
 | 
			
		||||
import { MiFollowRequest } from './FollowRequest.js';
 | 
			
		||||
import { MiAccessToken } from './AccessToken.js';
 | 
			
		||||
import { MiRole } from './Role.js';
 | 
			
		||||
 | 
			
		||||
export type MiNotification = {
 | 
			
		||||
	type: 'note';
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +67,11 @@ export type MiNotification = {
 | 
			
		|||
	id: string;
 | 
			
		||||
	createdAt: string;
 | 
			
		||||
	notifierId: MiUser['id'];
 | 
			
		||||
} | {
 | 
			
		||||
	type: 'roleAssigned';
 | 
			
		||||
	id: string;
 | 
			
		||||
	createdAt: string;
 | 
			
		||||
	roleId: MiRole['id'];
 | 
			
		||||
} | {
 | 
			
		||||
	type: 'achievementEarned';
 | 
			
		||||
	id: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -554,9 +554,7 @@ export const packedMeDetailedOnlySchema = {
 | 
			
		|||
				mention: notificationRecieveConfig,
 | 
			
		||||
				reaction: notificationRecieveConfig,
 | 
			
		||||
				pollEnded: notificationRecieveConfig,
 | 
			
		||||
				achievementEarned: notificationRecieveConfig,
 | 
			
		||||
				receiveFollowRequest: notificationRecieveConfig,
 | 
			
		||||
				followRequestAccepted: notificationRecieveConfig,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		emailNotificationTypes: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,11 +14,26 @@
 | 
			
		|||
 * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
 | 
			
		||||
 * receiveFollowRequest - フォローリクエストされた
 | 
			
		||||
 * followRequestAccepted - 自分の送ったフォローリクエストが承認された
 | 
			
		||||
 * roleAssigned - ロールが付与された
 | 
			
		||||
 * achievementEarned - 実績を獲得
 | 
			
		||||
 * app - アプリ通知
 | 
			
		||||
 * test - テスト通知(サーバー側)
 | 
			
		||||
 */
 | 
			
		||||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test'] as const;
 | 
			
		||||
export const notificationTypes = [
 | 
			
		||||
	'note',
 | 
			
		||||
	'follow',
 | 
			
		||||
	'mention',
 | 
			
		||||
	'reply',
 | 
			
		||||
	'renote',
 | 
			
		||||
	'quote',
 | 
			
		||||
	'reaction',
 | 
			
		||||
	'pollEnded',
 | 
			
		||||
	'receiveFollowRequest',
 | 
			
		||||
	'followRequestAccepted',
 | 
			
		||||
	'roleAssigned',
 | 
			
		||||
	'achievementEarned',
 | 
			
		||||
	'app',
 | 
			
		||||
	'test'] as const;
 | 
			
		||||
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
 | 
			
		||||
 | 
			
		||||
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ import { CacheService } from '@/core/CacheService.js';
 | 
			
		|||
import { IdService } from '@/core/IdService.js';
 | 
			
		||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
 | 
			
		||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
 | 
			
		||||
import { NotificationService } from '@/core/NotificationService.js';
 | 
			
		||||
import { sleep } from '../utils.js';
 | 
			
		||||
import type { TestingModule } from '@nestjs/testing';
 | 
			
		||||
import type { MockFunctionMetadata } from 'jest-mock';
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +33,7 @@ describe('RoleService', () => {
 | 
			
		|||
	let rolesRepository: RolesRepository;
 | 
			
		||||
	let roleAssignmentsRepository: RoleAssignmentsRepository;
 | 
			
		||||
	let metaService: jest.Mocked<MetaService>;
 | 
			
		||||
	let notificationService: jest.Mocked<NotificationService>;
 | 
			
		||||
	let clock: lolex.InstalledClock;
 | 
			
		||||
 | 
			
		||||
	function createUser(data: Partial<MiUser> = {}) {
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +78,8 @@ describe('RoleService', () => {
 | 
			
		|||
			.useMocker((token) => {
 | 
			
		||||
				if (token === MetaService) {
 | 
			
		||||
					return { fetch: jest.fn() };
 | 
			
		||||
				} else if (token === NotificationService) {
 | 
			
		||||
					return { createNotification: jest.fn() };
 | 
			
		||||
				}
 | 
			
		||||
				if (typeof token === 'function') {
 | 
			
		||||
					const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +97,7 @@ describe('RoleService', () => {
 | 
			
		|||
		roleAssignmentsRepository = app.get<RoleAssignmentsRepository>(DI.roleAssignmentsRepository);
 | 
			
		||||
 | 
			
		||||
		metaService = app.get<MetaService>(MetaService) as jest.Mocked<MetaService>;
 | 
			
		||||
		notificationService = app.get<NotificationService>(NotificationService) as jest.Mocked<NotificationService>;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	afterEach(async () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -273,4 +278,53 @@ describe('RoleService', () => {
 | 
			
		|||
			expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe('assign', () => {
 | 
			
		||||
		test('公開ロールの場合は通知される', async () => {
 | 
			
		||||
			const user = await createUser();
 | 
			
		||||
			const role = await createRole({
 | 
			
		||||
				isPublic: true,
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			await roleService.assign(user.id, role.id);
 | 
			
		||||
 | 
			
		||||
			await sleep(100);
 | 
			
		||||
 | 
			
		||||
			const assignments = await roleAssignmentsRepository.find({
 | 
			
		||||
				where: {
 | 
			
		||||
					userId: user.id,
 | 
			
		||||
					roleId: role.id,
 | 
			
		||||
				},
 | 
			
		||||
			});
 | 
			
		||||
			expect(assignments).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
			expect(notificationService.createNotification).toHaveBeenCalled();
 | 
			
		||||
			expect(notificationService.createNotification.mock.lastCall![0]).toBe(user.id);
 | 
			
		||||
			expect(notificationService.createNotification.mock.lastCall![1]).toBe('roleAssigned');
 | 
			
		||||
			expect(notificationService.createNotification.mock.lastCall![2]).toBe({
 | 
			
		||||
				roleId: role.id,
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test('非公開ロールの場合は通知されない', async () => {
 | 
			
		||||
			const user = await createUser();
 | 
			
		||||
			const role = await createRole({
 | 
			
		||||
				isPublic: false,
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			await roleService.assign(user.id, role.id);
 | 
			
		||||
 | 
			
		||||
			await sleep(100);
 | 
			
		||||
 | 
			
		||||
			const assignments = await roleAssignmentsRepository.find({
 | 
			
		||||
				where: {
 | 
			
		||||
					userId: user.id,
 | 
			
		||||
					roleId: role.id,
 | 
			
		||||
				},
 | 
			
		||||
			});
 | 
			
		||||
			expect(assignments).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
			expect(notificationService.createNotification).not.toHaveBeenCalled();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
	<div :class="$style.head">
 | 
			
		||||
		<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/>
 | 
			
		||||
		<MkAvatar v-else-if="notification.type === 'note'" :class="$style.icon" :user="notification.note.user" link preview/>
 | 
			
		||||
		<MkAvatar v-else-if="notification.type === 'roleAssigned'" :class="$style.icon" :user="$i" link preview/>
 | 
			
		||||
		<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
 | 
			
		||||
		<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
 | 
			
		||||
		<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
			<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
 | 
			
		||||
			<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
 | 
			
		||||
			<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
 | 
			
		||||
			<img v-else-if="notification.type === 'roleAssigned'" :src="notification.role.iconUrl" alt=""/>
 | 
			
		||||
			<!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
 | 
			
		||||
			<MkReactionIcon
 | 
			
		||||
				v-else-if="notification.type === 'reaction'"
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
		<header :class="$style.header">
 | 
			
		||||
			<span v-if="notification.type === 'pollEnded'">{{ i18n.ts._notification.pollEnded }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: <MkUserName :user="notification.note.user"/></span>
 | 
			
		||||
			<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
 | 
			
		||||
			<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
 | 
			
		||||
			<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
 | 
			
		||||
| 
						 | 
				
			
			@ -86,6 +89,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
 | 
			
		||||
				<i class="ti ti-quote" :class="$style.quote"></i>
 | 
			
		||||
			</MkA>
 | 
			
		||||
			<div v-else-if="notification.type === 'roleAssigned'" :class="$style.text">
 | 
			
		||||
				{{ notification.role.name }}
 | 
			
		||||
			</div>
 | 
			
		||||
			<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
 | 
			
		||||
				{{ i18n.ts._achievements._types['_' + notification.achievement].title }}
 | 
			
		||||
			</MkA>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,7 +54,21 @@ https://github.com/sindresorhus/file-type/blob/main/core.js
 | 
			
		|||
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const;
 | 
			
		||||
export const notificationTypes = [
 | 
			
		||||
	'note',
 | 
			
		||||
	'follow',
 | 
			
		||||
	'mention',
 | 
			
		||||
	'reply',
 | 
			
		||||
	'renote',
 | 
			
		||||
	'quote',
 | 
			
		||||
	'reaction',
 | 
			
		||||
	'pollEnded',
 | 
			
		||||
	'receiveFollowRequest',
 | 
			
		||||
	'followRequestAccepted',
 | 
			
		||||
	'roleAssigned',
 | 
			
		||||
	'achievementEarned',
 | 
			
		||||
	'app',
 | 
			
		||||
] as const;
 | 
			
		||||
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
 | 
			
		||||
 | 
			
		||||
export const ROLE_POLICIES = [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 | 
			
		|||
import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
 | 
			
		||||
import { notificationTypes } from '@/const.js';
 | 
			
		||||
 | 
			
		||||
const nonConfigurableNotificationTypes = ['note'];
 | 
			
		||||
const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned'];
 | 
			
		||||
 | 
			
		||||
const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
 | 
			
		||||
const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1635,9 +1635,6 @@ type FetchLike = (input: string, init?: {
 | 
			
		|||
// @public (undocumented)
 | 
			
		||||
type FetchRssRequest = operations['fetch-rss']['requestBody']['content']['application/json'];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
export const ffVisibility: readonly ["public", "followers", "private"];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
type Flash = components['schemas']['Flash'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1677,6 +1674,9 @@ type FlashUnlikeRequest = operations['flash/unlike']['requestBody']['content']['
 | 
			
		|||
// @public (undocumented)
 | 
			
		||||
type FlashUpdateRequest = operations['flash/update']['requestBody']['content']['application/json'];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
export const followersVisibilities: readonly ["public", "followers", "private"];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
type Following = components['schemas']['Following'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1725,6 +1725,9 @@ type FollowingUpdateRequest = operations['following/update']['requestBody']['con
 | 
			
		|||
// @public (undocumented)
 | 
			
		||||
type FollowingUpdateResponse = operations['following/update']['responses']['200']['content']['application/json'];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
export const followingVisibilities: readonly ["public", "followers", "private"];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
type GalleryFeaturedRequest = operations['gallery/featured']['requestBody']['content']['application/json'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2337,7 +2340,7 @@ type Notification_2 = components['schemas']['Notification'];
 | 
			
		|||
type NotificationsCreateRequest = operations['notifications/create']['requestBody']['content']['application/json'];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "achievementEarned"];
 | 
			
		||||
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"];
 | 
			
		||||
 | 
			
		||||
// @public (undocumented)
 | 
			
		||||
type Page = components['schemas']['Page'];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'achievementEarned'] as const;
 | 
			
		||||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned'] as const;
 | 
			
		||||
 | 
			
		||||
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue