1
1
mirror of https://github.com/kokonect-link/cherrypick synced 2024-11-23 14:46:44 +09:00

Merge remote-branch 'misskey/develop'

This commit is contained in:
NoriDev 2023-12-22 00:11:05 +09:00
commit 0a3e7413a3
141 changed files with 1119 additions and 266 deletions

View File

@ -14,7 +14,7 @@ jobs:
- run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4.0.0
uses: actions/setup-node@v4.0.1
with:
node-version-file: '.node-version'
cache: 'pnpm'

View File

@ -37,7 +37,7 @@ jobs:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.0
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -19,7 +19,7 @@ jobs:
with:
version: 8
run_install: false
- uses: actions/setup-node@v4.0.0
- uses: actions/setup-node@v4.0.1
with:
node-version-file: '.node-version'
cache: 'pnpm'
@ -46,7 +46,7 @@ jobs:
with:
version: 7
run_install: false
- uses: actions/setup-node@v4.0.0
- uses: actions/setup-node@v4.0.1
with:
node-version-file: '.node-version'
cache: 'pnpm'
@ -72,7 +72,7 @@ jobs:
with:
version: 7
run_install: false
- uses: actions/setup-node@v4.0.0
- uses: actions/setup-node@v4.0.1
with:
node-version-file: '.node-version'
cache: 'pnpm'

View File

@ -17,7 +17,7 @@ jobs:
services:
postgres:
image: postgres:13
image: postgres:15
ports:
- 54312:5432
env:
@ -38,7 +38,7 @@ jobs:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.0
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -26,7 +26,7 @@ jobs:
- run: corepack enable
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.0
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -25,7 +25,7 @@ jobs:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.0
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
@ -56,7 +56,7 @@ jobs:
services:
postgres:
image: postgres:13
image: postgres:15
ports:
- 54312:5432
env:
@ -83,7 +83,7 @@ jobs:
version: 7
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.0
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -28,7 +28,7 @@ jobs:
version: 8
run_install: false
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.0
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'

View File

@ -17,6 +17,7 @@
### Note
- Node.js 20.10.0が最小要件になりました
- 絵文字の追加辞書を既にインストールしている場合は、お手数ですが再インストールのほどお願いします
- 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。
**影響:**
@ -31,9 +32,12 @@
- 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
- Enhance: ローカリゼーションの更新
- Enhance: 依存関係の更新
- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正
### Client
@ -79,6 +83,7 @@
- Fix: AiScriptの`readline`が不正な値を返すことがある問題を修正
- Fix: 投票のみ/画像のみの引用RNが、通知欄でただのRNとして判定されるバグを修正
- Fix: CWをつけて引用RNしても、普通のRNとして扱われてしまうバグを修正しました。
- Fix: 「画像が1枚のみのメディアリストの高さ」を「デフォルト」以外に設定していると、CWの中などに添付された画像が見られないバグを修正
### Server
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
@ -97,6 +102,7 @@
- Fix: 「みつける」が年越し時に壊れる問題を修正
- Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正
- Fix: モデレーションログがモデレーターは閲覧できないように修正
- Fix: ハッシュタグのトレンド除外設定が即時に効果を持つように修正
- Fix: HTTP Digestヘッダのアルゴリズム部分に大文字の"SHA-256"しか使えない
- Fix: 管理者用APIのアクセス権限が適切に設定されていない問題を修正

View File

@ -27,7 +27,7 @@ spec:
ports:
- containerPort: 3000
- name: postgres
image: postgres:14-alpine
image: postgres:15-alpine
env:
- name: POSTGRES_USER
value: "example-cherrypick-user"
@ -38,7 +38,7 @@ spec:
ports:
- containerPort: 5432
- name: redis
image: redis:alpine
image: redis:7-alpine
ports:
- containerPort: 6379
volumes:

1
locales/index.d.ts vendored
View File

@ -2642,6 +2642,7 @@ export interface Locale {
"pollEnded": string;
"newNote": string;
"unreadAntennaNote": string;
"roleAssigned": string;
"emptyPushNotificationMessage": string;
"achievementEarned": string;
"testNotification": string;

View File

@ -2540,6 +2540,7 @@ _notification:
pollEnded: "アンケートの結果が出ました"
newNote: "新しい投稿"
unreadAntennaNote: "アンテナ {name}"
roleAssigned: "ロールが付与されました"
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
achievementEarned: "実績を獲得"
testNotification: "通知テスト"

View File

@ -974,8 +974,8 @@ makeReactionsPublicDescription: "나의 리액션을 누구나 볼 수 있게
classic: "클래식"
muteThread: "이 글타래 뮤트"
unmuteThread: "글타래 뮤트 해제"
ffVisibility: "내 인맥의 공개 범위"
ffVisibilityDescription: "나의 팔로우와 팔로워 정보에 대한 공개 범위를 설정할 수 있어요."
followingVisibility: "팔로우의 공개 범위"
followersVisibility: "팔로워의 공개 범위"
continueThread: "이 글타래 이어서 보기"
deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 돼요. 그래도 계속할까요? "
incorrectPassword: "비밀번호가 다른 것 같아요!"
@ -1281,7 +1281,8 @@ code: "코드"
reloadRequiredToApplySettings: "설정을 반영하려면 페이지를 다시 불러와야 해요."
remainingN: "남음: {n}"
overwriteContentConfirm: "현재 내용을 덮어쓰기 하게 돼요. 그래도 계속 진행할까요?"
seasonalScreenEffect: "계절 따른 화면 연출"
seasonalScreenEffect: "계절에 따른 화면 연출"
decorate: "장식하기"
showUnreadNotificationsCount: "읽지 않은 알림 수 표시"
showCatOnly: "고양이만 보기"
additionalPermissionsForFlash: "Play에 대한 추가 권한"
@ -2134,7 +2135,7 @@ _sfx:
channel: "채널 알림"
reaction: "리액션 선택"
_soundSettings:
driveFile: "드라이브에 있는 오디오 사용"
driveFile: "드라이브에 있는 오디오 파일 사용"
driveFileWarn: "드라이브에 있는 파일을 선택해 주세요."
driveFileTypeWarn: "이 파일은 지원되지 않는 형식이에요."
driveFileTypeWarnDescription: "오디오 파일을 선택해 주세요."

View File

@ -1231,7 +1231,7 @@ _initialTutorial:
skipAreYouSure: "結束教學模式?"
_landing:
title: "歡迎使用本教學課程"
description: "在這裡您可以查看CherryPick的基本使用方法和功能。"
description: "在這裡您可以查看 CherryPick 的基本使用方法和功能。"
_note:
title: "什麼是貼文?"
description: "在CherryPick上發布的內容稱為「貼文」。貼文在時間軸上按時間順序排列並即時更新。"

View File

@ -1,7 +1,7 @@
{
"name": "cherrypick",
"version": "4.6.0-beta.5",
"basedMisskeyVersion": "2023.12.0-beta.5",
"basedMisskeyVersion": "2023.12.0-beta.6",
"codename": "nasubi",
"repository": {
"type": "git",

View File

@ -77,6 +77,17 @@ export class FeaturedService {
return Array.from(ranking.keys());
}
@bindThis
private async removeFromRanking(name: string, windowRange: number, element: string): Promise<void> {
const currentWindow = this.getCurrentWindow(windowRange);
const previousWindow = currentWindow - 1;
const redisPipeline = this.redisClient.pipeline();
redisPipeline.zrem(`${name}:${currentWindow}`, element);
redisPipeline.zrem(`${name}:${previousWindow}`, element);
await redisPipeline.exec();
}
@bindThis
public updateGlobalNotesRanking(noteId: MiNote['id'], score = 1): Promise<void> {
return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
@ -126,4 +137,9 @@ export class FeaturedService {
public getHashtagsRanking(threshold: number): Promise<string[]> {
return this.getRankingOf('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, threshold);
}
@bindThis
public removeHashtagsFromRanking(hashtag: string): Promise<void> {
return this.removeFromRanking('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, hashtag);
}
}

View File

@ -11,6 +11,7 @@ import { MiMeta } from '@/models/Meta.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { bindThis } from '@/decorators.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
@ -25,6 +26,7 @@ export class MetaService implements OnApplicationShutdown {
@Inject(DI.db)
private db: DataSource,
private featuredService: FeaturedService,
private globalEventService: GlobalEventService,
) {
//this.onMessage = this.onMessage.bind(this);
@ -95,6 +97,8 @@ export class MetaService implements OnApplicationShutdown {
@bindThis
public async update(data: Partial<MiMeta>): Promise<MiMeta> {
let before: MiMeta | undefined;
const updated = await this.db.transaction(async transactionalEntityManager => {
const metas = await transactionalEntityManager.find(MiMeta, {
order: {
@ -102,10 +106,10 @@ export class MetaService implements OnApplicationShutdown {
},
});
const meta = metas[0];
before = metas[0];
if (meta) {
await transactionalEntityManager.update(MiMeta, meta.id, data);
if (before) {
await transactionalEntityManager.update(MiMeta, before.id, data);
const metas = await transactionalEntityManager.find(MiMeta, {
order: {
@ -119,6 +123,21 @@ export class MetaService implements OnApplicationShutdown {
}
});
if (data.hiddenTags) {
process.nextTick(() => {
const hiddenTags = new Set<string>(data.hiddenTags);
if (before) {
for (const previousHiddenTag of before.hiddenTags) {
hiddenTags.delete(previousHiddenTag);
}
}
for (const hiddenTag of hiddenTags) {
this.featuredService.removeHashtagsFromRanking(hiddenTag);
}
});
}
this.globalEventService.publishInternalEvent('metaUpdated', updated);
return updated;

View File

@ -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;
@ -80,14 +88,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,
@ -122,6 +133,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);
@ -430,6 +445,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', {

View File

@ -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 {

View File

@ -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';
import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js';
@ -28,8 +28,8 @@ const NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES = new Set(['note', 'mention', 're
export class NotificationEntityService implements OnModuleInit {
private userEntityService: UserEntityService;
private noteEntityService: NoteEntityService;
private roleEntityService: RoleEntityService;
private userGroupInvitationEntityService: UserGroupInvitationEntityService;
private customEmojiService: CustomEmojiService;
constructor(
private moduleRef: ModuleRef,
@ -49,15 +49,14 @@ export class NotificationEntityService implements OnModuleInit {
//private userEntityService: UserEntityService,
//private noteEntityService: NoteEntityService,
//private userGroupInvitationEntityService: UserGroupInvitationEntityService,
//private customEmojiService: CustomEmojiService,
) {
}
onModuleInit() {
this.userEntityService = this.moduleRef.get('UserEntityService');
this.noteEntityService = this.moduleRef.get('NoteEntityService');
this.roleEntityService = this.moduleRef.get('RoleEntityService');
this.userGroupInvitationEntityService = this.moduleRef.get('UserGroupInvitationEntityService');
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
}
@bindThis
@ -88,6 +87,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,
@ -99,6 +99,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'reaction' ? {
reaction: notification.reaction,
} : {}),
...(notification.type === 'roleAssigned' ? {
role: role,
} : {}),
// ...(notification.type === 'pollEnded' ? {
// note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
// detail: true,
@ -240,6 +243,8 @@ export class NotificationEntityService implements OnModuleInit {
});
}
const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
return await awaitAll({
id: notification.id,
createdAt: new Date(notification.createdAt).toISOString(),
@ -250,6 +255,9 @@ export class NotificationEntityService implements OnModuleInit {
...(notification.type === 'reaction' ? {
reaction: notification.reaction,
} : {}),
...(notification.type === 'roleAssigned' ? {
role: role,
} : {}),
...(notification.type === 'groupInvited' ? {
invitation: this.userGroupInvitationEntityService.pack(notification.userGroupInvitationId),
} : {}),

View File

@ -40,6 +40,7 @@ import { packedFlashSchema } from '@/models/json-schema/flash.js';
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
import { packedSigninSchema } from '@/models/json-schema/signin.js';
import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
import { packedAdSchema } from '@/models/json-schema/ad.js';
export const refs = {
UserLite: packedUserLiteSchema,
@ -52,6 +53,7 @@ export const refs = {
UserList: packedUserListSchema,
UserGroup: packedUserGroupSchema,
Ad: packedAdSchema,
Announcement: packedAnnouncementSchema,
App: packedAppSchema,
MessagingMessage: packedMessagingMessageSchema,

View File

@ -3,12 +3,11 @@
* 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 { MiUserGroupInvitation } from './UserGroupInvitation.js';
import { MiAccessToken } from './AccessToken.js';
import { MiRole } from './Role.js';
export type MiNotification = {
type: 'note';
@ -69,6 +68,11 @@ export type MiNotification = {
id: string;
createdAt: string;
notifierId: MiUser['id'];
} | {
type: 'roleAssigned';
id: string;
createdAt: string;
roleId: MiRole['id'];
} | {
type: 'groupInvited';
id: string;

View File

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const packedAdSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false,
nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
expiresAt: {
type: 'string',
optional: false,
nullable: false,
format: 'date-time',
},
startsAt: {
type: 'string',
optional: false,
nullable: false,
format: 'date-time',
},
place: {
type: 'string',
optional: false,
nullable: false,
},
priority: {
type: 'string',
optional: false,
nullable: false,
},
ratio: {
type: 'number',
optional: false,
nullable: false,
},
url: {
type: 'string',
optional: false,
nullable: false,
},
imageUrl: {
type: 'string',
optional: false,
nullable: false,
},
memo: {
type: 'string',
optional: false,
nullable: false,
},
dayOfWeek: {
type: 'integer',
optional: false,
nullable: false,
},
},
} as const;

View File

@ -566,9 +566,7 @@ export const packedMeDetailedOnlySchema = {
mention: notificationRecieveConfig,
reaction: notificationRecieveConfig,
pollEnded: notificationRecieveConfig,
achievementEarned: notificationRecieveConfig,
receiveFollowRequest: notificationRecieveConfig,
followRequestAccepted: notificationRecieveConfig,
groupInvited: notificationRecieveConfig,
},
},

View File

@ -155,8 +155,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.systemQueueWorker
.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => systemLogger.error(`error ${err}`, { e: renderError(err) }))
.on('failed', (job, err) => systemLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => systemLogger.error(`error ${err.stack}`, { e: renderError(err) }))
.on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`));
//#endregion
@ -194,8 +194,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.dbQueueWorker
.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => dbLogger.error(`error ${err}`, { e: renderError(err) }))
.on('failed', (job, err) => dbLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => dbLogger.error(`error ${err.stack}`, { e: renderError(err) }))
.on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`));
//#endregion
@ -218,8 +218,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.deliverQueueWorker
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
.on('error', (err: Error) => deliverLogger.error(`error ${err}`, { e: renderError(err) }))
.on('failed', (job, err) => deliverLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
.on('error', (err: Error) => deliverLogger.error(`error ${err.stack}`, { e: renderError(err) }))
.on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`));
//#endregion
@ -242,8 +242,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.inboxQueueWorker
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => inboxLogger.error(`error ${err}`, { e: renderError(err) }))
.on('failed', (job, err) => inboxLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => inboxLogger.error(`error ${err.stack}`, { e: renderError(err) }))
.on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`));
//#endregion
@ -266,8 +266,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.webhookDeliverQueueWorker
.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
.on('error', (err: Error) => webhookLogger.error(`error ${err}`, { e: renderError(err) }))
.on('failed', (job, err) => webhookLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
.on('error', (err: Error) => webhookLogger.error(`error ${err.stack}`, { e: renderError(err) }))
.on('stalled', (jobId) => webhookLogger.warn(`stalled id=${jobId}`));
//#endregion
@ -295,8 +295,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.relationshipQueueWorker
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => relationshipLogger.error(`error ${err}`, { e: renderError(err) }))
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => relationshipLogger.error(`error ${err.stack}`, { e: renderError(err) }))
.on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`));
//#endregion
@ -318,8 +318,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
this.objectStorageQueueWorker
.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => objectStorageLogger.error(`error ${err}`, { e: renderError(err) }))
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
.on('error', (err: Error) => objectStorageLogger.error(`error ${err.stack}`, { e: renderError(err) }))
.on('stalled', (jobId) => objectStorageLogger.warn(`stalled id=${jobId}`));
//#endregion

View File

@ -25,6 +25,11 @@ export const meta = {
id: 'cb865949-8af5-4062-a88c-ef55e8786d1d',
},
},
res: {
type: 'object',
optional: false, nullable: false,
ref: 'User',
},
} as const;
export const paramDef = {

View File

@ -17,6 +17,12 @@ export const meta = {
requireCredential: true,
requireModerator: true,
res: {
type: 'object',
optional: false,
nullable: false,
ref: 'Ad',
},
} as const;
export const paramDef = {
@ -63,7 +69,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
ad: ad,
});
return ad;
return {
id: ad.id,
expiresAt: ad.expiresAt.toISOString(),
startsAt: ad.startsAt.toISOString(),
dayOfWeek: ad.dayOfWeek,
url: ad.url,
imageUrl: ad.imageUrl,
priority: ad.priority,
ratio: ad.ratio,
place: ad.place,
memo: ad.memo,
};
});
}
}

View File

@ -16,6 +16,17 @@ export const meta = {
requireCredential: true,
requireModerator: true,
res: {
type: 'array',
optional: false,
nullable: false,
items: {
type: 'object',
optional: false,
nullable: false,
ref: 'Ad',
},
},
} as const;
export const paramDef = {
@ -46,7 +57,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
const ads = await query.limit(ps.limit).getMany();
return ads;
return ads.map(ad => ({
id: ad.id,
expiresAt: ad.expiresAt.toISOString(),
startsAt: ad.startsAt.toISOString(),
dayOfWeek: ad.dayOfWeek,
url: ad.url,
imageUrl: ad.imageUrl,
memo: ad.memo,
place: ad.place,
priority: ad.priority,
ratio: ad.ratio,
}));
});
}
}

View File

@ -31,6 +31,8 @@ export const meta = {
id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
},
},
ref: 'EmojiDetailed',
} as const;
export const paramDef = {

View File

@ -32,6 +32,8 @@ export const meta = {
id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
},
},
ref: 'EmojiDetailed',
} as const;
export const paramDef = {

View File

@ -15,6 +15,16 @@ export const meta = {
kind: 'read:admin',
tags: ['admin'],
res: {
type: 'array',
items: {
type: 'object',
properties: {
tablename: { type: 'string' },
indexname: { type: 'string' },
},
},
},
} as const;
export const paramDef = {

View File

@ -16,6 +16,25 @@ export const meta = {
requireCredential: true,
requireModerator: true,
res: {
type: 'array',
optional: false,
nullable: false,
items: {
type: 'object',
optional: false,
nullable: false,
properties: {
ip: { type: 'string' },
createdAt: {
type: 'string',
optional: false,
nullable: false,
format: 'date-time',
},
},
},
}
} as const;
export const paramDef = {

View File

@ -28,6 +28,20 @@ export const meta = {
id: '224eff5e-2488-4b18-b3e7-f50d94421648',
},
},
res: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', format: 'misskey:id' },
createdAt: { type: 'string', format: 'date-time' },
user: { ref: 'UserDetailed' },
expiresAt: { type: 'string', format: 'date-time', nullable: true },
},
required: ['id', 'createdAt', 'user'],
},
}
} as const;
export const paramDef = {
@ -80,7 +94,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: assign.id,
createdAt: this.idService.parse(assign.id).date.toISOString(),
user: await this.userEntityService.pack(assign.user!, me, { detail: true }),
expiresAt: assign.expiresAt,
expiresAt: assign.expiresAt?.toISOString() ?? null,
})));
});
}

View File

@ -11,6 +11,23 @@ export const meta = {
requireCredential: false,
tags: ['meta'],
res: {
type: 'object',
nullable: true,
properties: {
params: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: { type: 'string' },
},
},
},
},
},
} as const;
export const paramDef = {

View File

@ -18,6 +18,92 @@ export const meta = {
allowGet: true,
cacheSec: 60 * 60,
res: {
type: 'object',
optional: false,
nullable: false,
properties: {
topSubInstances: {
type: 'array',
optional: false,
nullable: false,
items: {
properties: {
id: { type: 'string' },
firstRetrievedAt: { type: 'string' },
host: { type: 'string' },
usersCount: { type: 'number' },
notesCount: { type: 'number' },
followingCount: { type: 'number' },
followersCount: { type: 'number' },
isNotResponding: { type: 'boolean' },
isSuspended: { type: 'boolean' },
isBlocked: { type: 'boolean' },
softwareName: { type: 'string' },
softwareVersion: { type: 'string' },
openRegistrations: { type: 'boolean' },
name: { type: 'string' },
description: { type: 'string' },
maintainerName: { type: 'string' },
maintainerEmail: { type: 'string' },
isSilenced: { type: 'boolean' },
iconUrl: { type: 'string' },
faviconUrl: { type: 'string' },
themeColor: { type: 'string' },
infoUpdatedAt: {
type: 'string',
nullable: true,
},
latestRequestReceivedAt: {
type: 'string',
nullable: true,
},
}
},
},
otherFollowersCount: { type: 'number' },
topPubInstances: {
type: 'array',
optional: false,
nullable: false,
items: {
properties: {
id: { type: 'string' },
firstRetrievedAt: { type: 'string' },
host: { type: 'string' },
usersCount: { type: 'number' },
notesCount: { type: 'number' },
followingCount: { type: 'number' },
followersCount: { type: 'number' },
isNotResponding: { type: 'boolean' },
isSuspended: { type: 'boolean' },
isBlocked: { type: 'boolean' },
softwareName: { type: 'string' },
softwareVersion: { type: 'string' },
openRegistrations: { type: 'boolean' },
name: { type: 'string' },
description: { type: 'string' },
maintainerName: { type: 'string' },
maintainerEmail: { type: 'string' },
isSilenced: { type: 'boolean' },
iconUrl: { type: 'string' },
faviconUrl: { type: 'string' },
themeColor: { type: 'string' },
infoUpdatedAt: {
type: 'string',
nullable: true,
},
latestRequestReceivedAt: {
type: 'string',
nullable: true,
},
}
},
},
otherFollowingCount: { type: 'number' },
},
}
} as const;
export const paramDef = {

View File

@ -32,6 +32,18 @@ export const meta = {
id: '693ba8ba-b486-40df-a174-72f8279b56a4',
},
},
res: {
type: 'object',
properties: {
type: {
type: 'string',
},
data: {
type: 'string',
},
},
},
} as const;
export const paramDef = {

View File

@ -16,6 +16,18 @@ export const meta = {
requireCredential: false,
allowGet: true,
cacheSec: 60 * 3,
res: {
type: 'object',
properties: {
items: {
type: 'array',
items: {
type: 'object',
},
}
}
},
} as const;
export const paramDef = {

View File

@ -27,6 +27,12 @@ export const meta = {
errors: {
},
res: {
type: 'object',
optional: false, nullable: false,
ref: 'Flash',
},
} as const;
export const paramDef = {

View File

@ -16,6 +16,16 @@ export const meta = {
requireCredential: false,
allowGet: true,
cacheSec: 60 * 1,
res: {
type: 'object',
optional: false, nullable: false,
properties: {
count: {
type: 'number',
nullable: false,
},
},
},
} as const;
export const paramDef = {

View File

@ -32,6 +32,16 @@ export const meta = {
id: '798d6847-b1ed-4f9c-b1f9-163c42655995',
},
},
res: {
type: 'object',
nullable: false,
optional: false,
properties: {
id: { type: 'string' },
name: { type: 'string' },
},
},
} as const;
export const paramDef = {

View File

@ -36,6 +36,140 @@ export const meta = {
id: 'bf32b864-449b-47b8-974e-f9a5468546f1',
},
},
res: {
type: 'object',
nullable: false,
optional: false,
properties: {
rp: {
type: 'object',
properties: {
id: {
type: 'string',
nullable: true,
},
},
},
user: {
type: 'object',
properties: {
id: {
type: 'string',
},
name: {
type: 'string',
},
displayName: {
type: 'string',
},
},
},
challenge: {
type: 'string',
},
pubKeyCredParams: {
type: 'array',
items: {
type: 'object',
properties: {
type: {
type: 'string',
},
alg: {
type: 'number',
},
},
},
},
timeout: {
type: 'number',
nullable: true,
},
excludeCredentials: {
type: 'array',
nullable: true,
items: {
type: 'object',
properties: {
id: {
type: 'string',
},
type: {
type: 'string',
},
transports: {
type: 'array',
items: {
type: 'string',
enum: [
"ble",
"cable",
"hybrid",
"internal",
"nfc",
"smart-card",
"usb",
],
},
},
},
},
},
authenticatorSelection: {
type: 'object',
nullable: true,
properties: {
authenticatorAttachment: {
type: 'string',
enum: [
"cross-platform",
"platform",
],
},
requireResidentKey: {
type: 'boolean',
},
userVerification: {
type: 'string',
enum: [
"discouraged",
"preferred",
"required",
],
},
},
},
attestation: {
type: 'string',
nullable: true,
enum: [
"direct",
"enterprise",
"indirect",
"none",
],
},
extensions: {
type: 'object',
nullable: true,
properties: {
appid: {
type: 'string',
nullable: true,
},
credProps: {
type: 'boolean',
nullable: true,
},
hmacCreateSecret: {
type: 'boolean',
nullable: true,
},
},
},
},
},
} as const;
export const paramDef = {

View File

@ -26,6 +26,19 @@ export const meta = {
id: '78d6c839-20c9-4c66-b90a-fc0542168b48',
},
},
res: {
type: 'object',
nullable: false,
optional: false,
properties: {
qr: { type: 'string' },
url: { type: 'string' },
secret: { type: 'string' },
label: { type: 'string' },
issuer: { type: 'string' },
},
},
} as const;
export const paramDef = {

View File

@ -13,6 +13,37 @@ export const meta = {
requireCredential: true,
secure: true,
res: {
type: 'array',
items: {
type: 'object',
properties: {
id: {
type: 'string',
format: 'misskey:id',
},
name: {
type: 'string',
},
createdAt: {
type: 'string',
format: 'date-time',
},
lastUsedAt: {
type: 'string',
format: 'date-time',
},
permission: {
type: 'array',
uniqueItems: true,
items: {
type: 'string'
},
}
},
},
},
} as const;
export const paramDef = {
@ -50,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: token.id,
name: token.name ?? token.app?.name,
createdAt: this.idService.parse(token.id).date.toISOString(),
lastUsedAt: token.lastUsedAt,
lastUsedAt: token.lastUsedAt?.toISOString(),
permission: token.permission,
})));
});

View File

@ -14,6 +14,36 @@ export const meta = {
requireCredential: true,
secure: true,
res: {
type: 'array',
items: {
type: 'object',
properties: {
id: {
type: 'string',
format: 'misskey:id',
},
name: {
type: 'string',
},
callbackUrl: {
type: 'string',
nullable: true,
},
permission: {
type: 'array',
uniqueItems: true,
items: {
type: 'string'
},
},
isAuthorized: {
type: 'boolean',
},
},
},
},
} as const;
export const paramDef = {

View File

@ -64,6 +64,10 @@ export const meta = {
id: 'b234a14e-9ebe-4581-8000-074b3c215962',
},
},
res: {
type: 'object',
},
} as const;
export const paramDef = {

View File

@ -9,6 +9,10 @@ import { RegistryApiService } from '@/core/RegistryApiService.js';
export const meta = {
requireCredential: true,
res: {
type: 'object',
},
} as const;
export const paramDef = {

View File

@ -18,6 +18,10 @@ export const meta = {
id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a',
},
},
res: {
type: 'object',
}
} as const;
export const paramDef = {

View File

@ -18,6 +18,10 @@ export const meta = {
id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a',
},
},
res: {
type: 'object',
}
} as const;
export const paramDef = {

View File

@ -3,12 +3,16 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { RegistryApiService } from '@/core/RegistryApiService.js';
export const meta = {
requireCredential: true,
res: {
type: 'object',
},
} as const;
export const paramDef = {

View File

@ -3,13 +3,35 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { RegistryApiService } from '@/core/RegistryApiService.js';
export const meta = {
requireCredential: true,
secure: true,
res: {
type: 'array',
items: {
type: 'object',
properties: {
scopes: {
type: 'array',
items: {
type: 'array',
items: {
type: 'string',
}
}
},
domain: {
type: 'string',
nullable: true,
},
},
},
}
} as const;
export const paramDef = {

View File

@ -40,6 +40,11 @@ export const meta = {
id: 'a2defefb-f220-8849-0af6-17f816099323',
},
},
res: {
type: 'object',
ref: 'UserDetailed',
},
} as const;
export const paramDef = {

View File

@ -27,6 +27,33 @@ export const meta = {
id: '87a9bb19-111e-4e37-81d3-a3e7426453b0',
},
},
res: {
type: 'object',
properties: {
id: {
type: 'string',
format: 'misskey:id'
},
userId: {
type: 'string',
format: 'misskey:id',
},
name: { type: 'string' },
on: {
type: 'array',
items: {
type: 'string',
enum: webhookEventTypes,
}
},
url: { type: 'string' },
secret: { type: 'string' },
active: { type: 'boolean' },
latestSentAt: { type: 'string', format: 'date-time', nullable: true },
latestStatus: { type: 'integer', nullable: true },
},
},
} as const;
export const paramDef = {
@ -73,7 +100,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.globalEventService.publishInternalEvent('webhookCreated', webhook);
return webhook;
return {
id: webhook.id,
userId: webhook.userId,
name: webhook.name,
on: webhook.on,
url: webhook.url,
secret: webhook.secret,
active: webhook.active,
latestSentAt: webhook.latestSentAt?.toISOString(),
latestStatus: webhook.latestStatus,
};
});
}
}

View File

@ -5,6 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { webhookEventTypes } from '@/models/Webhook.js';
import type { WebhooksRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
@ -14,6 +15,36 @@ export const meta = {
requireCredential: true,
kind: 'read:account',
res: {
type: 'array',
items: {
type: 'object',
properties: {
id: {
type: 'string',
format: 'misskey:id'
},
userId: {
type: 'string',
format: 'misskey:id',
},
name: { type: 'string' },
on: {
type: 'array',
items: {
type: 'string',
enum: webhookEventTypes,
}
},
url: { type: 'string' },
secret: { type: 'string' },
active: { type: 'boolean' },
latestSentAt: { type: 'string', format: 'date-time', nullable: true },
latestStatus: { type: 'integer', nullable: true },
},
}
}
} as const;
export const paramDef = {
@ -33,7 +64,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userId: me.id,
});
return webhooks;
return webhooks.map(webhook => (
{
id: webhook.id,
userId: webhook.userId,
name: webhook.name,
on: webhook.on,
url: webhook.url,
secret: webhook.secret,
active: webhook.active,
latestSentAt: webhook.latestSentAt?.toISOString(),
latestStatus: webhook.latestStatus,
}
));
});
}
}

View File

@ -5,6 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { webhookEventTypes } from '@/models/Webhook.js';
import type { WebhooksRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
@ -23,6 +24,33 @@ export const meta = {
id: '50f614d9-3047-4f7e-90d8-ad6b2d5fb098',
},
},
res: {
type: 'object',
properties: {
id: {
type: 'string',
format: 'misskey:id',
},
userId: {
type: 'string',
format: 'misskey:id',
},
name: { type: 'string' },
on: {
type: 'array',
items: {
type: 'string',
enum: webhookEventTypes,
},
},
url: { type: 'string' },
secret: { type: 'string' },
active: { type: 'boolean' },
latestSentAt: { type: 'string', format: 'date-time', nullable: true },
latestStatus: { type: 'integer', nullable: true },
},
},
} as const;
export const paramDef = {
@ -49,7 +77,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchWebhook);
}
return webhook;
return {
id: webhook.id,
userId: webhook.userId,
name: webhook.name,
on: webhook.on,
url: webhook.url,
secret: webhook.secret,
active: webhook.active,
latestSentAt: webhook.latestSentAt?.toISOString(),
latestStatus: webhook.latestStatus,
};
});
}
}

View File

@ -24,6 +24,25 @@ export const meta = {
id: '30aaaee3-4792-48dc-ab0d-cf501a575ac5',
},
},
res: {
type: 'array',
items: {
type: 'object',
nullable: false,
properties: {
id: {
type: 'string',
format: 'misskey:id',
},
user: {
type: 'object',
ref: 'User',
},
},
required: ['id', 'user'],
},
},
} as const;
export const paramDef = {

View File

@ -15,6 +15,53 @@ export const meta = {
cacheSec: 60 * 1,
tags: ['meta'],
res: {
type: 'object',
optional: false, nullable: false,
properties: {
machine: {
type: 'string',
nullable: false,
},
cpu: {
type: 'object',
nullable: false,
properties: {
model: {
type: 'string',
nullable: false,
},
cores: {
type: 'number',
nullable: false,
},
},
},
mem: {
type: 'object',
properties: {
total: {
type: 'number',
nullable: false,
},
},
},
fs: {
type: 'object',
nullable: false,
properties: {
total: {
type: 'number',
nullable: false,
},
used: {
type: 'number',
nullable: false,
},
},
},
},
},
} as const;
export const paramDef = {

View File

@ -12,6 +12,30 @@ export const meta = {
description: 'Endpoint for testing input validation.',
requireCredential: false,
res: {
type: 'object',
properties: {
id: {
type: 'string',
format: 'misskey:id',
},
required: {
type: 'boolean',
},
string: {
type: 'string',
},
default: {
type: 'string',
},
nullableDefault: {
type: 'string',
default: 'hello',
nullable: true,
},
},
},
} as const;
export const paramDef = {

View File

@ -10,6 +10,21 @@ import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
res: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string',
},
unlockedAt: {
type: 'number',
},
},
},
}
} as const;
export const paramDef = {

View File

@ -25,6 +25,35 @@ export const meta = {
id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686',
},
},
res: {
type: 'array',
items: {
type: 'object',
nullable: false,
properties: {
id: {
type: 'string',
format: 'misskey:id',
},
createdAt: {
type: 'string',
format: 'date-time',
},
userId: {
type: 'string',
format: 'misskey:id',
},
user: {
type: 'object',
ref: 'User',
},
withReplies: {
type: 'boolean',
},
},
},
},
} as const;
export const paramDef = {

View File

@ -14,12 +14,28 @@
* pollEnded -
* receiveFollowRequest -
* followRequestAccepted -
* roleAssigned -
* groupInvited -
* achievementEarned -
* app -
* test -
*/
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'achievementEarned', 'app', 'test'] as const;
export const notificationTypes = [
'note',
'follow',
'mention',
'reply',
'renote',
'quote',
'reaction',
'pollEnded',
'receiveFollowRequest',
'followRequestAccepted',
'roleAssigned',
'groupInvited',
'achievementEarned',
'app',
'test'] as const;
export const obsoleteNotificationTypes = ['pollVote'/*, 'groupInvited'*/] as const;
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;

View File

@ -7,7 +7,7 @@ services:
- "127.0.0.1:56312:6379"
dbtest:
image: postgres:13
image: postgres:15
ports:
- "127.0.0.1:54312:5432"
environment:

View File

@ -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();
});
});
});

View File

@ -2486,7 +2486,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'];

View File

@ -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;

View File

@ -3,16 +3,16 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent, App } from 'vue';
import { computed, watch, version as vueVersion, App } from 'vue';
import { compareVersions } from 'compare-versions';
import widgets from '@/widgets/index.js';
import directives from '@/directives/index.js';
import components from '@/components/index.js';
import { version, basedMisskeyVersion, ui, lang, updateLocale, locale } from '@/config.js';
import { version, basedMisskeyVersion, lang, updateLocale, locale } from '@/config.js';
import { applyTheme } from '@/scripts/theme.js';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
import { i18n, updateI18n } from '@/i18n.js';
import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js';
import { updateI18n } from '@/i18n.js';
import { $i, refreshAccount, login } from '@/account.js';
import { defaultStore, ColdDeviceStorage } from '@/store.js';
import { fetchInstance, instance } from '@/instance.js';
import { deviceKind } from '@/scripts/device-kind.js';

View File

@ -3,14 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue';
import { createApp, markRaw, defineAsyncComponent } from 'vue';
import { common } from './common.js';
import { version, ui, lang, updateLocale } from '@/config.js';
import { i18n, updateI18n } from '@/i18n.js';
import { ui } from '@/config.js';
import { i18n } from '@/i18n.js';
import { confirm, alert, post, popup, welcomeToast } from '@/os.js';
import { useStream } from '@/stream.js';
import * as sound from '@/scripts/sound.js';
import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js';
import { $i, updateAccount, signout } from '@/account.js';
import { defaultStore, ColdDeviceStorage } from '@/store.js';
import { makeHotkey } from '@/scripts/hotkey.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
@ -20,7 +20,6 @@ import { mainRouter } from '@/router.js';
import { initializeSw } from '@/scripts/initialize-sw.js';
import { deckStore } from '@/ui/deck/deck-store.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { SnowfallEffect } from '@/scripts/snowfall-effect.js';
import { userName } from '@/filters/user.js';
import { vibrate } from '@/scripts/vibrate.js';
@ -82,6 +81,7 @@ export async function mainBoot() {
if (defaultStore.state.enableSeasonalScreenEffect) {
const month = new Date().getMonth() + 1;
if (month === 12 || month === 1) {
const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
new SnowfallEffect().render();
}
}

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue';
import { createApp, defineAsyncComponent } from 'vue';
import { common } from './common.js';
export async function subBoot() {

View File

@ -24,8 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
import { i18n } from '@/i18n.js';
import { ref, shallowRef, toRefs } from 'vue';
const props = defineProps<{
modelValue: string | null;

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div ref="root">
<div>
<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/>
<div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container">
<div
@ -27,41 +27,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
<script lang="ts">
/**
* アスペクト比算出のためにHTMLElement.clientWidthを使うが
* 大変重たいのでコンテナ要素とメディアリスト幅のペアをキャッシュする
* タイムラインごとにスクロールコンテナが存在する前提だが
*/
const widthCache = new Map<Element, number>();
/**
* コンテナ要素がリサイズされたらキャッシュを削除する
*/
const ro = new ResizeObserver(entries => {
for (const entry of entries) {
widthCache.delete(entry.target);
}
});
async function getClientWidthWithCache(targetEl: HTMLElement, containerEl: HTMLElement, count = 0) {
if (_DEV_) console.log('getClientWidthWithCache', { targetEl, containerEl, count, cache: widthCache.get(containerEl) });
if (widthCache.has(containerEl)) return widthCache.get(containerEl)!;
const width = targetEl.clientWidth;
if (count <= 10 && width < 64) {
// width64
await new Promise(resolve => setTimeout(resolve, 50));
return getClientWidthWithCache(targetEl, containerEl, count + 1);
}
widthCache.set(containerEl, width);
ro.observe(containerEl);
return width;
}
</script>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, shallowRef } from 'vue';
import * as Misskey from 'cherrypick-js';
@ -74,15 +39,12 @@ import XVideo from '@/components/MkMediaVideo.vue';
import * as os from '@/os.js';
import { FILE_TYPE_BROWSERSAFE } from '@/const';
import { defaultStore } from '@/store.js';
import { getScrollContainer, getBodyScrollHeight } from '@/scripts/scroll.js';
const props = defineProps<{
mediaList: Misskey.entities.DriveFile[];
raw?: boolean;
}>();
const root = shallowRef<HTMLDivElement>();
const container = shallowRef<HTMLElement | null | undefined>(undefined);
const gallery = shallowRef<HTMLDivElement>();
const pswpZIndex = os.claimZIndex('middle');
document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
@ -95,12 +57,8 @@ const popstateHandler = (): void => {
}
};
/**
* アスペクト比をmediaListWithOneImageAppearanceに基づいていい感じに調整する
* aspect-ratioではなくheightを使う
*/
async function calcAspectRatio() {
if (!gallery.value || !root.value) return;
if (!gallery.value) return;
let img = props.mediaList[0];
@ -109,41 +67,22 @@ async function calcAspectRatio() {
return;
}
if (!container.value) container.value = getScrollContainer(root.value);
const width = container.value ? await getClientWidthWithCache(root.value, container.value) : root.value.clientWidth;
const heightMin = (ratio: number) => {
const imgResizeRatio = width / img.properties.width;
const imgDrawHeight = img.properties.height * imgResizeRatio;
const maxHeight = width * ratio;
const height = Math.min(imgDrawHeight, maxHeight);
if (_DEV_) console.log('Image height calculated:', { width, properties: img.properties, imgResizeRatio, imgDrawHeight, maxHeight, height });
return `${height}px`;
};
const ratioMax = (ratio: number) => `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`;
switch (defaultStore.state.mediaListWithOneImageAppearance) {
case '16_9':
gallery.value.style.height = heightMin(9 / 16);
gallery.value.style.aspectRatio = ratioMax(16 / 9);
break;
case '1_1':
gallery.value.style.height = heightMin(1);
gallery.value.style.aspectRatio = ratioMax(1 / 1);
break;
case '2_3':
gallery.value.style.height = heightMin(3 / 2);
gallery.value.style.aspectRatio = ratioMax(2 / 3);
break;
default: {
const maxHeight = Math.max(64, (container.value ? container.value.clientHeight : getBodyScrollHeight()) * 0.5 || 360);
if (width === 0 || !maxHeight) return;
const imgResizeRatio = width / img.properties.width;
const imgDrawHeight = img.properties.height * imgResizeRatio;
gallery.value.style.height = `${Math.max(64, Math.min(imgDrawHeight, maxHeight))}px`;
gallery.value.style.minHeight = 'initial';
gallery.value.style.maxHeight = 'initial';
default:
gallery.value.style.aspectRatio = '';
break;
}
}
gallery.value.style.aspectRatio = 'initial';
}
onMounted(() => {

View File

@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { Ref, computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import { computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import { focusPrev, focusNext } from '@/scripts/focus.js';
import MkSwitchButton from '@/components/MkSwitch.button.vue';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu.js';

View File

@ -198,7 +198,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, ref, shallowRef, Ref, defineAsyncComponent, watch, provide } from 'vue';
import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
import * as mfm from 'cherrypick-mfm-js';
import * as Misskey from 'cherrypick-js';
import MkNoteSub from '@/components/MkNoteSub.vue';

View File

@ -303,11 +303,10 @@ import { useNoteCapture } from '@/scripts/use-note-capture.js';
import { deepClone } from '@/scripts/clone.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue';
import { miLocalStorage } from '@/local-storage.js';

View File

@ -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>
@ -38,6 +39,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'"
@ -52,6 +54,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>
@ -88,6 +91,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>
@ -139,7 +145,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef } from 'vue';
import { ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkFollowButton from '@/components/MkFollowButton.vue';

View File

@ -24,13 +24,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated, watch } from 'vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated } from 'vue';
import MkPagination from '@/components/MkPagination.vue';
import XNotification from '@/components/MkNotification.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import MkNote from '@/components/MkNote.vue';
import { useStream } from '@/stream.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { notificationTypes } from '@/const.js';
import { infoImageUrl } from '@/instance.js';

View File

@ -23,8 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, watch, ref, shallowRef } from 'vue';
import { deviceKind } from '@/scripts/device-kind.js';
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
import { i18n } from '@/i18n.js';
import { getScrollContainer } from '@/scripts/scroll.js';
import { vibrate } from '@/scripts/vibrate.js';

View File

@ -83,7 +83,6 @@ import { ref, computed } from 'vue';
import { toUnicode } from 'punycode/';
import MkButton from './MkButton.vue';
import MkInput from './MkInput.vue';
import MkSwitch from './MkSwitch.vue';
import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
import * as config from '@/config.js';
import * as os from '@/os.js';

View File

@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, onMounted, ref, watch } from 'vue';
import { computed, ref } from 'vue';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';

View File

@ -39,7 +39,6 @@ import XSignup from '@/components/MkSignupDialog.form.vue';
import XServerRules from '@/components/MkSignupDialog.rules.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
const props = withDefaults(defineProps<{
autoSet?: boolean;

View File

@ -34,15 +34,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import XUser from '@/components/MkUserSetupDialog.User.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
import { $i } from '@/account.js';
import MkPagination from '@/components/MkPagination.vue';
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };

View File

@ -44,14 +44,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { instance } from '@/instance.js';
import { ref, watch } from 'vue';
import { i18n } from '@/i18n.js';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js';
import { $i } from '@/account.js';
const isLocked = ref(false);
const hideOnlineStatus = ref(false);

View File

@ -30,8 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { instance } from '@/instance.js';
import { ref, watch } from 'vue';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';

View File

@ -29,7 +29,6 @@ import * as Misskey from 'cherrypick-js';
import { ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import * as os from '@/os.js';
const props = defineProps<{

View File

@ -54,7 +54,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import XTimeline from './welcome.timeline.vue';
import XSigninDialog from '@/components/MkSigninDialog.vue';
import XSignupDialog from '@/components/MkSignupDialog.vue';
import MkButton from '@/components/MkButton.vue';
@ -65,7 +64,6 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { mainRouter } from '@/router.js';
import number from '@/filters/number.js';
import MkNumber from '@/components/MkNumber.vue';
import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue';

View File

@ -21,7 +21,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch } from 'vue';
import MkButton from '@/components/MkButton.vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{

View File

@ -14,7 +14,6 @@ import { computed } from 'vue';
import * as os from '@/os.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { url } from '@/config.js';
import { popout as popout_ } from '@/scripts/popout.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router.js';
import { defaultStore } from '@/store.js';

View File

@ -4,11 +4,8 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
import MkAd from './MkAd.vue';
import { i18n } from '@/i18n.js';
let lock: Promise<undefined> | undefined;

View File

@ -5,7 +5,6 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest';
import { userEvent, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes';
import MkUserName from './MkUserName.vue';

View File

@ -16,7 +16,6 @@ import * as mfm from 'cherrypick-mfm-js';
import * as Misskey from 'cherrypick-js';
import { TextBlock } from './block.type';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
import { $i } from '@/account.js';
const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));

View File

@ -10,7 +10,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, nextTick } from 'vue';
import * as Misskey from 'cherrypick-js';
import XBlock from './page.block.vue';

View File

@ -54,7 +54,22 @@ 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', 'groupInvited', 'achievementEarned', 'app'] as const;
export const notificationTypes = [
'note',
'follow',
'mention',
'reply',
'renote',
'quote',
'reaction',
'pollEnded',
'receiveFollowRequest',
'followRequestAccepted',
'roleAssigned',
'groupInvited',
'achievementEarned',
'app',
] as const;
export const obsoleteNotificationTypes = ['pollVote'/*, 'groupInvited'*/] as const;
export const ROLE_POLICIES = [

View File

@ -218,12 +218,12 @@ import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
import { url } from '@/config.js';
import { userPage, acct } from '@/filters/user.js';
import { acct } from '@/filters/user.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import { iAmAdmin, $i } from '@/account.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkPagination from '@/components/MkPagination.vue';
import { globalEvents } from '@/events.js';
const props = withDefaults(defineProps<{

View File

@ -97,11 +97,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, computed } from 'vue';
import JSON5 from 'json5';
import XHeader from './_header_.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import FormSection from '@/components/form/section.vue';
import FormSplit from '@/components/form/split.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js';
import { instance, fetchInstance } from '@/instance.js';

View File

@ -64,8 +64,6 @@ import XHeader from './_header_.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import FormSection from '@/components/form/section.vue';
import FormSplit from '@/components/form/split.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js';
import { fetchInstance } from '@/instance.js';

View File

@ -123,9 +123,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'cherrypick-js';
import { CodeDiff } from 'v-code-diff';
import JSON5 from 'json5';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js';
import MkFolder from '@/components/MkFolder.vue';
const props = defineProps<{

View File

@ -69,7 +69,7 @@ import { useRouter } from '@/router.js';
import MkButton from '@/components/MkButton.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkPagination from '@/components/MkPagination.vue';
import { infoImageUrl } from '@/instance.js';
const router = useRouter();

View File

@ -16,8 +16,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, watch } from 'vue';
import * as os from '@/os.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';

View File

@ -46,9 +46,6 @@ import { ref, computed } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';

View File

@ -86,7 +86,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { customEmojiCategories } from '@/custom-emojis.js';
import MkSwitch from '@/components/MkSwitch.vue';
import { selectFile, selectFiles } from '@/scripts/select-file.js';
import { selectFile } from '@/scripts/select-file.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
const props = defineProps<{

View File

@ -68,7 +68,7 @@ import MkInput from '@/components/MkInput.vue';
import { userListsCache } from '@/cache.js';
import { $i } from '@/account.js';
import { defaultStore } from '@/store.js';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkPagination from '@/components/MkPagination.vue';
const {
enableInfiniteScroll,

View File

@ -29,7 +29,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
expanded?: boolean;

View File

@ -47,18 +47,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { ref } from 'vue';
import MkNotes from '@/components/MkNotes.vue';
import MkInput from '@/components/MkInput.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { $i } from '@/account.js';
import { instance } from '@/instance.js';
import MkInfo from '@/components/MkInfo.vue';
import { useRouter } from '@/router.js';
import MkFolder from '@/components/MkFolder.vue';

Some files were not shown because too many files have changed in this diff Show More