summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-07-13 16:16:13 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-07-13 16:16:13 +0900
commit12456b22c53d6247ffb0687d63343acaa35d61b3 (patch)
treef11ac449e3131f316f2d66e957289ea04b301c9f
parentUpdate about-misskey.vue (diff)
parentrefactor(backend): `core/activitypub` (#11247) (diff)
downloadmisskey-12456b22c53d6247ffb0687d63343acaa35d61b3.tar.gz
misskey-12456b22c53d6247ffb0687d63343acaa35d61b3.tar.bz2
misskey-12456b22c53d6247ffb0687d63343acaa35d61b3.zip
Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop
-rw-r--r--CHANGELOG.md6
-rw-r--r--gulpfile.mjs (renamed from gulpfile.js)14
-rw-r--r--locales/generateDTS.js8
-rw-r--r--locales/index.d.ts4
-rw-r--r--locales/index.js8
-rw-r--r--locales/ja-JP.yml2
-rw-r--r--locales/package.json3
-rw-r--r--packages/backend/src/core/FetchInstanceMetadataService.ts2
-rw-r--r--packages/backend/src/core/NoteCreateService.ts20
-rw-r--r--packages/backend/src/core/NoteReadService.ts14
-rw-r--r--packages/backend/src/core/SignupService.ts4
-rw-r--r--packages/backend/src/core/UserFollowingService.ts40
-rw-r--r--packages/backend/src/core/activitypub/ApAudienceService.ts20
-rw-r--r--packages/backend/src/core/activitypub/ApDbResolverService.ts22
-rw-r--r--packages/backend/src/core/activitypub/ApDeliverManagerService.ts51
-rw-r--r--packages/backend/src/core/activitypub/ApInboxService.ts61
-rw-r--r--packages/backend/src/core/activitypub/ApMfmService.ts9
-rw-r--r--packages/backend/src/core/activitypub/ApRendererService.ts98
-rw-r--r--packages/backend/src/core/activitypub/ApRequestService.ts4
-rw-r--r--packages/backend/src/core/activitypub/ApResolverService.ts8
-rw-r--r--packages/backend/src/core/activitypub/LdSignatureService.ts49
-rw-r--r--packages/backend/src/core/activitypub/misc/contexts.ts10
-rw-r--r--packages/backend/src/core/activitypub/models/ApNoteService.ts2
-rw-r--r--packages/backend/src/core/activitypub/type.ts1
-rw-r--r--packages/backend/src/core/entities/ChannelEntityService.ts31
-rw-r--r--packages/backend/src/core/entities/ClipEntityService.ts2
-rw-r--r--packages/backend/src/core/entities/FlashEntityService.ts2
-rw-r--r--packages/backend/src/core/entities/GalleryPostEntityService.ts2
-rw-r--r--packages/backend/src/core/entities/NoteEntityService.ts14
-rw-r--r--packages/backend/src/core/entities/PageEntityService.ts2
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts12
-rw-r--r--packages/backend/src/server/ActivityPubServerService.ts21
-rw-r--r--packages/backend/src/server/api/SignupApiService.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/admin/promo/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/admin/roles/update.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/auth/accept.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/blocking/create.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/blocking/delete.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/clips/add-note.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/clips/favorite.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/check-existence.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/flash/like.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/following/create.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/following/delete.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/like.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-antennas.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/i/read-announcement.ts14
-rw-r--r--packages/backend/src/server/api/endpoints/i/revoke-token.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/mute/create.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts20
-rw-r--r--packages/backend/src/server/api/endpoints/notes/favorites/create.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/pages/like.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/promo/read.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/users/followers.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/users/following.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts28
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/favorite.ts20
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/push.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/show.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts10
-rw-r--r--packages/backend/src/server/api/stream/channels/user-list.ts10
-rw-r--r--packages/frontend/.storybook/changes.ts9
-rw-r--r--packages/frontend/.storybook/main.ts9
-rw-r--r--packages/frontend/.storybook/package.json3
-rw-r--r--packages/frontend/.storybook/preload-locale.ts7
-rw-r--r--packages/frontend/.storybook/preload-theme.ts7
-rw-r--r--packages/frontend/.storybook/preview.ts8
-rw-r--r--packages/frontend/.storybook/tsconfig.json2
-rw-r--r--packages/frontend/src/cache.ts1
-rw-r--r--packages/frontend/src/components/MkAvatars.vue15
-rw-r--r--packages/frontend/src/components/MkMediaList.vue2
-rw-r--r--packages/frontend/src/components/global/MkA.stories.impl.ts4
-rw-r--r--packages/frontend/src/components/global/MkAd.stories.impl.ts6
-rw-r--r--packages/frontend/src/pages/channel.vue5
-rw-r--r--packages/frontend/src/pages/custom-emojis-manager.vue4
-rw-r--r--packages/frontend/src/pages/my-antennas/create.vue6
-rw-r--r--packages/frontend/src/pages/my-antennas/edit.vue6
-rw-r--r--packages/frontend/src/pages/my-antennas/index.vue89
-rw-r--r--packages/frontend/src/pages/my-lists/index.vue53
-rw-r--r--packages/frontend/src/pages/settings/index.vue2
-rw-r--r--packages/frontend/src/pages/settings/migration.vue3
-rw-r--r--packages/frontend/src/scripts/cache.ts12
-rw-r--r--packages/frontend/src/scripts/get-user-menu.ts44
-rw-r--r--packages/frontend/src/style.scss2
-rw-r--r--packages/sw/.eslintrc.cjs (renamed from packages/sw/.eslintrc.js)0
-rw-r--r--packages/sw/build.js9
-rw-r--r--packages/sw/package.json3
-rw-r--r--packages/sw/src/sw.ts13
88 files changed, 692 insertions, 488 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c28d0b9bfb..8d6dd3a7db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,11 @@
- 見たことのあるRenoteを省略して表示をオンのときに自分のnoteのrenoteを省略するように
- フォルダーやファイルに対しても開発者モード使用時、IDをコピーできるように
- 引用対象を「もっと見る」で展開した場合、「閉じる」で畳めるように
+- プロフィールURLをコピーできるボタンを追加 #11190
+- ユーザーのContextMenuに「アンテナに追加」ボタンを追加
+- フォローやお気に入り登録をしていないチャンネルを開く時は概要ページを開くように
+- 画面ビューワをタップした場合、マウスクリックと同様に画像ビューワを閉じるように
+- オフライン時の画面にリロードボタンを追加
- Fix: サーバーメトリクスが90度傾いている
- Fix: 非ログイン時にクレデンシャルが必要なページに行くとエラーが出る問題を修正
- Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正
@@ -33,6 +38,7 @@
- Fix: ページ遷移でスクロール位置が保持されない問題を修正
- Fix: フォルダーのページネーションが機能しない #11180
- Fix: 長い文章を投稿する際、プレビューが画面からはみ出る問題を修正
+- Fix: システムフォント設定が正しく反映されない問題を修正
### Server
- JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました
diff --git a/gulpfile.js b/gulpfile.mjs
index 6507aad60e..9556eb795f 100644
--- a/gulpfile.js
+++ b/gulpfile.mjs
@@ -2,14 +2,14 @@
* Gulp tasks
*/
-const fs = require('fs');
-const gulp = require('gulp');
-const replace = require('gulp-replace');
-const terser = require('gulp-terser');
-const cssnano = require('gulp-cssnano');
+import * as fs from 'node:fs';
+import gulp from 'gulp';
+import replace from 'gulp-replace';
+import terser from 'gulp-terser';
+import cssnano from 'gulp-cssnano';
-const locales = require('./locales');
-const meta = require('./package.json');
+import locales from './locales/index.js';
+import meta from './package.json' assert { type: "json" };
gulp.task('copy:backend:views', () =>
gulp.src('./packages/backend/src/server/web/views/**/*').pipe(gulp.dest('./packages/backend/built/server/web/views'))
diff --git a/locales/generateDTS.js b/locales/generateDTS.js
index 5949aee7cd..bc98276325 100644
--- a/locales/generateDTS.js
+++ b/locales/generateDTS.js
@@ -1,6 +1,6 @@
-const fs = require('fs');
-const yaml = require('js-yaml');
-const ts = require('typescript');
+import * as fs from 'node:fs';
+import * as yaml from 'js-yaml';
+import * as ts from 'typescript';
function createMembers(record) {
return Object.entries(record)
@@ -14,7 +14,7 @@ function createMembers(record) {
));
}
-module.exports = function generateDTS() {
+export default function generateDTS() {
const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
const members = createMembers(locale);
const elements = [
diff --git a/locales/index.d.ts b/locales/index.d.ts
index dacab0cca5..7555984b24 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -52,6 +52,7 @@ export interface Locale {
"deleteAndEdit": string;
"deleteAndEditConfirm": string;
"addToList": string;
+ "addToAntenna": string;
"sendMessage": string;
"copyRSS": string;
"copyUsername": string;
@@ -59,6 +60,7 @@ export interface Locale {
"copyNoteId": string;
"copyFileId": string;
"copyFolderId": string;
+ "copyProfileUrl": string;
"searchUser": string;
"reply": string;
"loadMore": string;
@@ -2156,4 +2158,4 @@ export interface Locale {
declare const locales: {
[lang: string]: Locale;
};
-export = locales;
+export default locales;
diff --git a/locales/index.js b/locales/index.js
index 2248bb6ac9..7801f1275b 100644
--- a/locales/index.js
+++ b/locales/index.js
@@ -2,8 +2,8 @@
* Languages Loader
*/
-const fs = require('fs');
-const yaml = require('js-yaml');
+import * as fs from 'node:fs';
+import * as yaml from 'js-yaml';
const merge = (...args) => args.reduce((a, c) => ({
...a,
@@ -51,9 +51,9 @@ const primaries = {
// 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く
const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), '');
-const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/${c}.yml`, 'utf-8'))) || {}, a), {});
+const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {});
-module.exports = Object.entries(locales)
+export default Object.entries(locales)
.reduce((a, [k ,v]) => (a[k] = (() => {
const [lang] = k.split('-');
switch (k) {
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 29f157b849..82efc8a469 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -49,6 +49,7 @@ delete: "削除"
deleteAndEdit: "削除して編集"
deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、Renote、返信も全て削除されます。"
addToList: "リストに追加"
+addToAntenna: "アンテナに追加"
sendMessage: "メッセージを送信"
copyRSS: "RSSをコピー"
copyUsername: "ユーザー名をコピー"
@@ -56,6 +57,7 @@ copyUserId: "ユーザーIDをコピー"
copyNoteId: "ノートIDをコピー"
copyFileId: "ファイルIDをコピー"
copyFolderId: "フォルダーIDをコピー"
+copyProfileUrl: "プロフィールURLをコピー"
searchUser: "ユーザーを検索"
reply: "返信"
loadMore: "もっと見る"
diff --git a/locales/package.json b/locales/package.json
new file mode 100644
index 0000000000..bedb411a91
--- /dev/null
+++ b/locales/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index a0afaaf888..9e8d17442f 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -103,7 +103,7 @@ export class FetchInstanceMetadataService {
if (name) updates.name = name;
if (description) updates.description = description;
- if (icon || favicon) updates.iconUrl = icon ?? favicon;
+ if (icon || favicon) updates.iconUrl = (icon && !icon.includes('data:image/png;base64')) ? icon : favicon;
if (favicon) updates.faviconUrl = favicon;
if (themeColor) updates.themeColor = themeColor;
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index fb36ca3b6a..4f3d66dd58 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -570,12 +570,14 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.reply) {
// 通知
if (data.reply.userHost === null) {
- const threadMuted = await this.noteThreadMutingsRepository.findOneBy({
- userId: data.reply.userId,
- threadId: data.reply.threadId ?? data.reply.id,
+ const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ where: {
+ userId: data.reply.userId,
+ threadId: data.reply.threadId ?? data.reply.id,
+ }
});
- if (!threadMuted) {
+ if (!isThreadMuted) {
nm.push(data.reply.userId, 'reply');
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
@@ -712,12 +714,14 @@ export class NoteCreateService implements OnApplicationShutdown {
@bindThis
private async createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) {
for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
- const threadMuted = await this.noteThreadMutingsRepository.findOneBy({
- userId: u.id,
- threadId: note.threadId ?? note.id,
+ const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ where: {
+ userId: u.id,
+ threadId: note.threadId ?? note.id,
+ },
});
- if (threadMuted) {
+ if (isThreadMuted) {
continue;
}
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
index b84591e26d..52e9bd369a 100644
--- a/packages/backend/src/core/NoteReadService.ts
+++ b/packages/backend/src/core/NoteReadService.ts
@@ -43,11 +43,13 @@ export class NoteReadService implements OnApplicationShutdown {
//#endregion
// スレッドミュート
- const threadMute = await this.noteThreadMutingsRepository.findOneBy({
- userId: userId,
- threadId: note.threadId ?? note.id,
+ const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+ where: {
+ userId: userId,
+ threadId: note.threadId ?? note.id,
+ },
});
- if (threadMute) return;
+ if (isThreadMuted) return;
const unread = {
id: this.idService.genId(),
@@ -62,9 +64,9 @@ export class NoteReadService implements OnApplicationShutdown {
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
- const exist = await this.noteUnreadsRepository.findOneBy({ id: unread.id });
+ const exist = await this.noteUnreadsRepository.exist({ where: { id: unread.id } });
- if (exist == null) return;
+ if (!exist) return;
if (params.isMentioned) {
this.globalEventService.publishMainStream(userId, 'unreadMention', note.id);
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index e6b17a8741..1e44406c16 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -71,12 +71,12 @@ export class SignupService {
const secret = generateUserToken();
// Check username duplication
- if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) {
+ if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
throw new Error('DUPLICATED_USERNAME');
}
// Check deleted username duplication
- if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) {
+ if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
throw new Error('USED_USERNAME');
}
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 7d90bc2c08..4d7bfeaf23 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -122,22 +122,26 @@ export class UserFollowingService implements OnModuleInit {
let autoAccept = false;
// 鍵アカウントであっても、既にフォローされていた場合はスルー
- const following = await this.followingsRepository.findOneBy({
- followerId: follower.id,
- followeeId: followee.id,
+ const isFollowing = await this.followingsRepository.exist({
+ where: {
+ followerId: follower.id,
+ followeeId: followee.id,
+ },
});
- if (following) {
+ if (isFollowing) {
autoAccept = true;
}
// フォローしているユーザーは自動承認オプション
if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
- const followed = await this.followingsRepository.findOneBy({
- followerId: followee.id,
- followeeId: follower.id,
+ const isFollowed = await this.followingsRepository.exist({
+ where: {
+ followerId: followee.id,
+ followeeId: follower.id,
+ },
});
- if (followed) autoAccept = true;
+ if (isFollowed) autoAccept = true;
}
// Automatically accept if the follower is an account who has moved and the locked followee had accepted the old account.
@@ -206,12 +210,14 @@ export class UserFollowingService implements OnModuleInit {
this.cacheService.userFollowingsCache.refresh(follower.id);
- const req = await this.followRequestsRepository.findOneBy({
- followeeId: followee.id,
- followerId: follower.id,
+ const requestExist = await this.followRequestsRepository.exist({
+ where: {
+ followeeId: followee.id,
+ followerId: follower.id,
+ },
});
- if (req) {
+ if (requestExist) {
await this.followRequestsRepository.delete({
followeeId: followee.id,
followerId: follower.id,
@@ -505,12 +511,14 @@ export class UserFollowingService implements OnModuleInit {
}
}
- const request = await this.followRequestsRepository.findOneBy({
- followeeId: followee.id,
- followerId: follower.id,
+ const requestExist = await this.followRequestsRepository.exist({
+ where: {
+ followeeId: followee.id,
+ followerId: follower.id,
+ },
});
- if (request == null) {
+ if (!requestExist) {
throw new IdentifiableError('17447091-ce07-46dd-b331-c1fd4f15b1e7', 'request not found');
}
diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts
index 0eab7fa335..a4ab5eae20 100644
--- a/packages/backend/src/core/activitypub/ApAudienceService.ts
+++ b/packages/backend/src/core/activitypub/ApAudienceService.ts
@@ -16,6 +16,8 @@ type AudienceInfo = {
visibleUsers: User[],
};
+type GroupedAudience = Record<'public' | 'followers' | 'other', string[]>;
+
@Injectable()
export class ApAudienceService {
constructor(
@@ -67,11 +69,11 @@ export class ApAudienceService {
}
@bindThis
- private groupingAudience(ids: string[], actor: RemoteUser) {
- const groups = {
- public: [] as string[],
- followers: [] as string[],
- other: [] as string[],
+ private groupingAudience(ids: string[], actor: RemoteUser): GroupedAudience {
+ const groups: GroupedAudience = {
+ public: [],
+ followers: [],
+ other: [],
};
for (const id of ids) {
@@ -90,7 +92,7 @@ export class ApAudienceService {
}
@bindThis
- private isPublic(id: string) {
+ private isPublic(id: string): boolean {
return [
'https://www.w3.org/ns/activitystreams#Public',
'as#Public',
@@ -99,9 +101,7 @@ export class ApAudienceService {
}
@bindThis
- private isFollowers(id: string, actor: RemoteUser) {
- return (
- id === (actor.followersUri ?? `${actor.uri}/followers`)
- );
+ private isFollowers(id: string, actor: RemoteUser): boolean {
+ return id === (actor.followersUri ?? `${actor.uri}/followers`);
}
}
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index 20283a163c..d5a530c903 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -99,13 +99,15 @@ export class ApDbResolverService implements OnApplicationShutdown {
if (parsed.local) {
if (parsed.type !== 'users') return null;
- return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
- id: parsed.id,
- }).then(x => x ?? undefined)) as LocalUser | undefined ?? null;
+ return await this.cacheService.userByIdCache.fetchMaybe(
+ parsed.id,
+ () => this.usersRepository.findOneBy({ id: parsed.id }).then(x => x ?? undefined),
+ ) as LocalUser | undefined ?? null;
} else {
- return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
- uri: parsed.uri,
- })) as RemoteUser | null;
+ return await this.cacheService.uriPersonCache.fetch(
+ parsed.uri,
+ () => this.usersRepository.findOneBy({ uri: parsed.uri }),
+ ) as RemoteUser | null;
}
}
@@ -145,9 +147,11 @@ export class ApDbResolverService implements OnApplicationShutdown {
} | null> {
const user = await this.apPersonService.resolvePerson(uri) as RemoteUser;
- if (user == null) return null;
-
- const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null);
+ const key = await this.publicKeyByUserIdCache.fetch(
+ user.id,
+ () => this.userPublickeysRepository.findOneBy({ userId: user.id }),
+ v => v != null,
+ );
return {
user,
diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
index 82c2c9f71f..ae1e42bf53 100644
--- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
+++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
@@ -52,7 +52,7 @@ export class ApDeliverManagerService {
* @param activity Activity
*/
@bindThis
- public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity) {
+ public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity): Promise<void> {
const manager = new DeliverManager(
this.userEntityService,
this.followingsRepository,
@@ -71,7 +71,7 @@ export class ApDeliverManagerService {
* @param to Target user
*/
@bindThis
- public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser) {
+ public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser): Promise<void> {
const manager = new DeliverManager(
this.userEntityService,
this.followingsRepository,
@@ -84,7 +84,7 @@ export class ApDeliverManagerService {
}
@bindThis
- public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null) {
+ public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null): DeliverManager {
return new DeliverManager(
this.userEntityService,
this.followingsRepository,
@@ -118,6 +118,7 @@ class DeliverManager {
activity: IActivity | null,
) {
// 型で弾いてはいるが一応ローカルユーザーかチェック
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (actor.host != null) throw new Error('actor.host must be null');
// パフォーマンス向上のためキューに突っ込むのはidのみに絞る
@@ -131,10 +132,10 @@ class DeliverManager {
* Add recipe for followers deliver
*/
@bindThis
- public addFollowersRecipe() {
- const deliver = {
+ public addFollowersRecipe(): void {
+ const deliver: IFollowersRecipe = {
type: 'Followers',
- } as IFollowersRecipe;
+ };
this.addRecipe(deliver);
}
@@ -144,11 +145,11 @@ class DeliverManager {
* @param to To
*/
@bindThis
- public addDirectRecipe(to: RemoteUser) {
- const recipe = {
+ public addDirectRecipe(to: RemoteUser): void {
+ const recipe: IDirectRecipe = {
type: 'Direct',
to,
- } as IDirectRecipe;
+ };
this.addRecipe(recipe);
}
@@ -158,7 +159,7 @@ class DeliverManager {
* @param recipe Recipe
*/
@bindThis
- public addRecipe(recipe: IRecipe) {
+ public addRecipe(recipe: IRecipe): void {
this.recipes.push(recipe);
}
@@ -166,17 +167,13 @@ class DeliverManager {
* Execute delivers
*/
@bindThis
- public async execute() {
+ public async execute(): Promise<void> {
// The value flags whether it is shared or not.
// key: inbox URL, value: whether it is sharedInbox
const inboxes = new Map<string, boolean>();
- /*
- build inbox list
-
- Process follower recipes first to avoid duplication when processing
- direct recipes later.
- */
+ // build inbox list
+ // Process follower recipes first to avoid duplication when processing direct recipes later.
if (this.recipes.some(r => isFollowers(r))) {
// followers deliver
// TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
@@ -190,26 +187,24 @@ class DeliverManager {
followerSharedInbox: true,
followerInbox: true,
},
- }) as {
- followerSharedInbox: string | null;
- followerInbox: string;
- }[];
+ });
for (const following of followers) {
const inbox = following.followerSharedInbox ?? following.followerInbox;
+ if (inbox === null) throw new Error('inbox is null');
inboxes.set(inbox, following.followerSharedInbox != null);
}
}
- this.recipes.filter((recipe): recipe is IDirectRecipe =>
- // followers recipes have already been processed
- isDirect(recipe)
+ for (const recipe of this.recipes.filter(isDirect)) {
// check that shared inbox has not been added yet
- && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox))
+ if (recipe.to.sharedInbox !== null && inboxes.has(recipe.to.sharedInbox)) continue;
+
// check that they actually have an inbox
- && recipe.to.inbox != null,
- )
- .forEach(recipe => inboxes.set(recipe.to.inbox!, false));
+ if (recipe.to.inbox === null) continue;
+
+ inboxes.set(recipe.to.inbox, false);
+ }
// deliver
this.queueService.deliverMany(this.actor, this.activity, inboxes);
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index efef777fb0..8d5f4883e4 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -21,10 +21,10 @@ import { CacheService } from '@/core/CacheService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { QueueService } from '@/core/QueueService.js';
-import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, } from '@/models/index.js';
+import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
import type { RemoteUser } from '@/models/entities/User.js';
-import { getApHrefNullable, getApId, getApIds, getApType, getOneApHrefNullable, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
+import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
import { ApNoteService } from './models/ApNoteService.js';
import { ApLoggerService } from './ApLoggerService.js';
import { ApDbResolverService } from './ApDbResolverService.js';
@@ -86,7 +86,7 @@ export class ApInboxService {
}
@bindThis
- public async performActivity(actor: RemoteUser, activity: IObject) {
+ public async performActivity(actor: RemoteUser, activity: IObject): Promise<void> {
if (isCollectionOrOrderedCollection(activity)) {
const resolver = this.apResolverService.createResolver();
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
@@ -107,7 +107,7 @@ export class ApInboxService {
if (actor.uri) {
if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
setImmediate(() => {
- this.apPersonService.updatePerson(actor.uri!);
+ this.apPersonService.updatePerson(actor.uri);
});
}
}
@@ -229,7 +229,7 @@ export class ApInboxService {
@bindThis
private async add(actor: RemoteUser, activity: IAdd): Promise<void> {
- if ('actor' in activity && actor.uri !== activity.actor) {
+ if (actor.uri !== activity.actor) {
throw new Error('invalid actor');
}
@@ -273,7 +273,7 @@ export class ApInboxService {
const unlock = await this.appLockService.getApLock(uri);
try {
- // 既に同じURIを持つものが登録されていないかチェック
+ // 既に同じURIを持つものが登録されていないかチェック
const exist = await this.apNoteService.fetchNote(uri);
if (exist) {
return;
@@ -292,7 +292,7 @@ export class ApInboxService {
return;
}
- this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`);
+ this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode}`);
}
throw err;
}
@@ -409,7 +409,7 @@ export class ApInboxService {
@bindThis
private async delete(actor: RemoteUser, activity: IDelete): Promise<string> {
- if ('actor' in activity && actor.uri !== activity.actor) {
+ if (actor.uri !== activity.actor) {
throw new Error('invalid actor');
}
@@ -420,7 +420,7 @@ export class ApInboxService {
// typeが不明だけど、どうせ消えてるのでremote resolveしない
formerType = undefined;
} else {
- const object = activity.object as IObject;
+ const object = activity.object;
if (isTombstone(object)) {
formerType = toSingle(object.formerType);
} else {
@@ -503,7 +503,10 @@ export class ApInboxService {
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
const uris = getApIds(activity.object);
- const userIds = uris.filter(uri => uri.startsWith(this.config.url + '/users/')).map(uri => uri.split('/').pop()!);
+ const userIds = uris
+ .filter(uri => uri.startsWith(this.config.url + '/users/'))
+ .map(uri => uri.split('/').at(-1))
+ .filter((userId): userId is string => userId !== undefined);
const users = await this.usersRepository.findBy({
id: In(userIds),
});
@@ -566,7 +569,7 @@ export class ApInboxService {
@bindThis
private async remove(actor: RemoteUser, activity: IRemove): Promise<void> {
- if ('actor' in activity && actor.uri !== activity.actor) {
+ if (actor.uri !== activity.actor) {
throw new Error('invalid actor');
}
@@ -586,7 +589,7 @@ export class ApInboxService {
@bindThis
private async undo(actor: RemoteUser, activity: IUndo): Promise<string> {
- if ('actor' in activity && actor.uri !== activity.actor) {
+ if (actor.uri !== activity.actor) {
throw new Error('invalid actor');
}
@@ -618,12 +621,14 @@ export class ApInboxService {
return 'skip: follower not found';
}
- const following = await this.followingsRepository.findOneBy({
- followerId: follower.id,
- followeeId: actor.id,
+ const isFollowing = await this.followingsRepository.exist({
+ where: {
+ followerId: follower.id,
+ followeeId: actor.id,
+ },
});
- if (following) {
+ if (isFollowing) {
await this.userFollowingService.unfollow(follower, actor);
return 'ok: unfollowed';
}
@@ -673,22 +678,26 @@ export class ApInboxService {
return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません';
}
- const req = await this.followRequestsRepository.findOneBy({
- followerId: actor.id,
- followeeId: followee.id,
+ const requestExist = await this.followRequestsRepository.exist({
+ where: {
+ followerId: actor.id,
+ followeeId: followee.id,
+ },
});
- const following = await this.followingsRepository.findOneBy({
- followerId: actor.id,
- followeeId: followee.id,
+ const isFollowing = await this.followingsRepository.exist({
+ where: {
+ followerId: actor.id,
+ followeeId: followee.id,
+ },
});
- if (req) {
+ if (requestExist) {
await this.userFollowingService.cancelFollowRequest(followee, actor);
return 'ok: follow request canceled';
}
- if (following) {
+ if (isFollowing) {
await this.userFollowingService.unfollow(actor, followee);
return 'ok: unfollowed';
}
@@ -713,7 +722,7 @@ export class ApInboxService {
@bindThis
private async update(actor: RemoteUser, activity: IUpdate): Promise<string> {
- if ('actor' in activity && actor.uri !== activity.actor) {
+ if (actor.uri !== activity.actor) {
return 'skip: invalid actor';
}
@@ -727,7 +736,7 @@ export class ApInboxService {
});
if (isActor(object)) {
- await this.apPersonService.updatePerson(actor.uri!, resolver, object);
+ await this.apPersonService.updatePerson(actor.uri, resolver, object);
return 'ok: Person updated';
} else if (getApType(object) === 'Question') {
await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err));
diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts
index 411cf17d19..d7269eca92 100644
--- a/packages/backend/src/core/activitypub/ApMfmService.ts
+++ b/packages/backend/src/core/activitypub/ApMfmService.ts
@@ -4,9 +4,9 @@ import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { MfmService } from '@/core/MfmService.js';
import type { Note } from '@/models/entities/Note.js';
+import { bindThis } from '@/decorators.js';
import { extractApHashtagObjects } from './models/tag.js';
import type { IObject } from './type.js';
-import { bindThis } from '@/decorators.js';
@Injectable()
export class ApMfmService {
@@ -19,14 +19,13 @@ export class ApMfmService {
}
@bindThis
- public htmlToMfm(html: string, tag?: IObject | IObject[]) {
- const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null);
-
+ public htmlToMfm(html: string, tag?: IObject | IObject[]): string {
+ const hashtagNames = extractApHashtagObjects(tag).map(x => x.name);
return this.mfmService.fromHtml(html, hashtagNames);
}
@bindThis
- public getNoteHtml(note: Note) {
+ public getNoteHtml(note: Note): string | null {
if (!note.text) return '';
return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers));
}
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index d8b95ca4d1..95e3a936d2 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -1,6 +1,6 @@
import { createPublicKey } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
-import { In, IsNull } from 'typeorm';
+import { In } from 'typeorm';
import { v4 as uuid } from 'uuid';
import * as mfm from 'mfm-js';
import { DI } from '@/di-symbols.js';
@@ -26,7 +26,6 @@ import { isNotNull } from '@/misc/is-not-null.js';
import { LdSignatureService } from './LdSignatureService.js';
import { ApMfmService } from './ApMfmService.js';
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
-import type { IIdentifier } from './models/identifier.js';
@Injectable()
export class ApRendererService {
@@ -63,7 +62,7 @@ export class ApRendererService {
}
@bindThis
- public renderAccept(object: any, user: { id: User['id']; host: null }): IAccept {
+ public renderAccept(object: string | IObject, user: { id: User['id']; host: null }): IAccept {
return {
type: 'Accept',
actor: this.userEntityService.genLocalUserUri(user.id),
@@ -72,7 +71,7 @@ export class ApRendererService {
}
@bindThis
- public renderAdd(user: LocalUser, target: any, object: any): IAdd {
+ public renderAdd(user: LocalUser, target: string | IObject | undefined, object: string | IObject): IAdd {
return {
type: 'Add',
actor: this.userEntityService.genLocalUserUri(user.id),
@@ -82,7 +81,7 @@ export class ApRendererService {
}
@bindThis
- public renderAnnounce(object: any, note: Note): IAnnounce {
+ public renderAnnounce(object: string | IObject, note: Note): IAnnounce {
const attributedTo = this.userEntityService.genLocalUserUri(note.userId);
let to: string[] = [];
@@ -133,13 +132,13 @@ export class ApRendererService {
@bindThis
public renderCreate(object: IObject, note: Note): ICreate {
- const activity = {
+ const activity: ICreate = {
id: `${this.config.url}/notes/${note.id}/activity`,
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Create',
published: note.createdAt.toISOString(),
object,
- } as ICreate;
+ };
if (object.to) activity.to = object.to;
if (object.cc) activity.cc = object.cc;
@@ -209,7 +208,7 @@ export class ApRendererService {
* @param id Follower|Followee ID
*/
@bindThis
- public async renderFollowUser(id: User['id']) {
+ public async renderFollowUser(id: User['id']): Promise<string> {
const user = await this.usersRepository.findOneByOrFail({ id: id }) as PartialLocalUser | PartialRemoteUser;
return this.userEntityService.getUserUri(user);
}
@@ -223,8 +222,8 @@ export class ApRendererService {
return {
id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`,
type: 'Follow',
- actor: this.userEntityService.getUserUri(follower)!,
- object: this.userEntityService.getUserUri(followee)!,
+ actor: this.userEntityService.getUserUri(follower),
+ object: this.userEntityService.getUserUri(followee),
};
}
@@ -264,14 +263,14 @@ export class ApRendererService {
public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }): Promise<ILike> {
const reaction = noteReaction.reaction;
- const object = {
+ const object: ILike = {
type: 'Like',
id: `${this.config.url}/likes/${noteReaction.id}`,
actor: `${this.config.url}/users/${noteReaction.userId}`,
object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`,
content: reaction,
_misskey_reaction: reaction,
- } as ILike;
+ };
if (reaction.startsWith(':')) {
const name = reaction.replaceAll(':', '');
@@ -287,7 +286,7 @@ export class ApRendererService {
public renderMention(mention: PartialLocalUser | PartialRemoteUser): IApMention {
return {
type: 'Mention',
- href: this.userEntityService.getUserUri(mention)!,
+ href: this.userEntityService.getUserUri(mention),
name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as LocalUser).username}`,
};
}
@@ -297,8 +296,8 @@ export class ApRendererService {
src: PartialLocalUser | PartialRemoteUser,
dst: PartialLocalUser | PartialRemoteUser,
): IMove {
- const actor = this.userEntityService.getUserUri(src)!;
- const target = this.userEntityService.getUserUri(dst)!;
+ const actor = this.userEntityService.getUserUri(src);
+ const target = this.userEntityService.getUserUri(dst);
return {
id: `${this.config.url}/moves/${src.id}/${dst.id}`,
actor,
@@ -310,10 +309,10 @@ export class ApRendererService {
@bindThis
public async renderNote(note: Note, dive = true): Promise<IPost> {
- const getPromisedFiles = async (ids: string[]) => {
- if (!ids || ids.length === 0) return [];
+ const getPromisedFiles = async (ids: string[]): Promise<DriveFile[]> => {
+ if (ids.length === 0) return [];
const items = await this.driveFilesRepository.findBy({ id: In(ids) });
- return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[];
+ return ids.map(id => items.find(item => item.id === id)).filter((item): item is DriveFile => item != null);
};
let inReplyTo;
@@ -323,9 +322,9 @@ export class ApRendererService {
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
if (inReplyToNote != null) {
- const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId });
+ const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } });
- if (inReplyToUser != null) {
+ if (inReplyToUserExist) {
if (inReplyToNote.uri) {
inReplyTo = inReplyToNote.uri;
} else {
@@ -375,7 +374,7 @@ export class ApRendererService {
id: In(note.mentions),
}) : [];
- const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag));
+ const hashtagTags = note.tags.map(tag => this.renderHashtag(tag));
const mentionTags = mentionedUsers.map(u => this.renderMention(u as LocalUser | RemoteUser));
const files = await getPromisedFiles(note.fileIds);
@@ -451,37 +450,26 @@ export class ApRendererService {
@bindThis
public async renderPerson(user: LocalUser) {
const id = this.userEntityService.genLocalUserUri(user.id);
- const isSystem = !!user.username.match(/\./);
+ const isSystem = user.username.includes('.');
const [avatar, banner, profile] = await Promise.all([
- user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined),
- user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined),
+ user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : undefined,
+ user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : undefined,
this.userProfilesRepository.findOneByOrFail({ userId: user.id }),
]);
- const attachment: {
+ const attachment = profile.fields.map(field => ({
type: 'PropertyValue',
- name: string,
- value: string,
- identifier?: IIdentifier,
- }[] = [];
-
- if (profile.fields) {
- for (const field of profile.fields) {
- attachment.push({
- type: 'PropertyValue',
- name: field.name,
- value: (field.value != null && field.value.match(/^https?:/))
- ? `<a href="${new URL(field.value).href}" rel="me nofollow noopener" target="_blank">${new URL(field.value).href}</a>`
- : field.value,
- });
- }
- }
+ name: field.name,
+ value: /^https?:/.test(field.value)
+ ? `<a href="${new URL(field.value).href}" rel="me nofollow noopener" target="_blank">${new URL(field.value).href}</a>`
+ : field.value,
+ }));
const emojis = await this.getEmojis(user.emojis);
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
- const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag));
+ const hashtagTags = user.tags.map(tag => this.renderHashtag(tag));
const tag = [
...apemojis,
@@ -490,7 +478,7 @@ export class ApRendererService {
const keypair = await this.userKeypairService.getUserKeypair(user.id);
- const person = {
+ const person: any = {
type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person',
id,
inbox: `${id}/inbox`,
@@ -508,11 +496,11 @@ export class ApRendererService {
image: banner ? this.renderImage(banner) : null,
tag,
manuallyApprovesFollowers: user.isLocked,
- discoverable: !!user.isExplorable,
+ discoverable: user.isExplorable,
publicKey: this.renderKey(user, keypair, '#main-key'),
isCat: user.isCat,
attachment: attachment.length ? attachment : undefined,
- } as any;
+ };
if (user.movedToUri) {
person.movedTo = user.movedToUri;
@@ -552,7 +540,7 @@ export class ApRendererService {
}
@bindThis
- public renderReject(object: any, user: { id: User['id'] }): IReject {
+ public renderReject(object: string | IObject, user: { id: User['id'] }): IReject {
return {
type: 'Reject',
actor: this.userEntityService.genLocalUserUri(user.id),
@@ -561,7 +549,7 @@ export class ApRendererService {
}
@bindThis
- public renderRemove(user: { id: User['id'] }, target: any, object: any): IRemove {
+ public renderRemove(user: { id: User['id'] }, target: string | IObject | undefined, object: string | IObject): IRemove {
return {
type: 'Remove',
actor: this.userEntityService.genLocalUserUri(user.id),
@@ -579,8 +567,8 @@ export class ApRendererService {
}
@bindThis
- public renderUndo(object: any, user: { id: User['id'] }): IUndo {
- const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined;
+ public renderUndo(object: string | IObject, user: { id: User['id'] }): IUndo {
+ const id = typeof object !== 'string' && typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined;
return {
type: 'Undo',
@@ -592,7 +580,7 @@ export class ApRendererService {
}
@bindThis
- public renderUpdate(object: any, user: { id: User['id'] }): IUpdate {
+ public renderUpdate(object: string | IObject, user: { id: User['id'] }): IUpdate {
return {
id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`,
actor: this.userEntityService.genLocalUserUri(user.id),
@@ -658,7 +646,7 @@ export class ApRendererService {
vcard: 'http://www.w3.org/2006/vcard/ns#',
},
],
- }, x as T & { id: string; });
+ }, x as T & { id: string });
}
@bindThis
@@ -683,13 +671,13 @@ export class ApRendererService {
*/
@bindThis
public renderOrderedCollectionPage(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) {
- const page = {
+ const page: any = {
id,
partOf,
type: 'OrderedCollectionPage',
totalItems,
orderedItems,
- } as any;
+ };
if (prev) page.prev = prev;
if (next) page.next = next;
@@ -706,7 +694,7 @@ export class ApRendererService {
* @param orderedItems attached objects (optional)
*/
@bindThis
- public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) {
+ public renderOrderedCollection(id: string, totalItems: number, first?: string, last?: string, orderedItems?: IObject[]) {
const page: any = {
id,
type: 'OrderedCollection',
@@ -722,7 +710,7 @@ export class ApRendererService {
@bindThis
private async getEmojis(names: string[]): Promise<Emoji[]> {
- if (names == null || names.length === 0) return [];
+ if (names.length === 0) return [];
const allEmojis = await this.customEmojiService.localEmojisCache.fetch();
const emojis = names.map(name => allEmojis.get(name)).filter(isNotNull);
diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts
index 5005612ab8..44676cac20 100644
--- a/packages/backend/src/core/activitypub/ApRequestService.ts
+++ b/packages/backend/src/core/activitypub/ApRequestService.ts
@@ -140,7 +140,7 @@ export class ApRequestService {
}
@bindThis
- public async signedPost(user: { id: User['id'] }, url: string, object: any) {
+ public async signedPost(user: { id: User['id'] }, url: string, object: unknown): Promise<void> {
const body = JSON.stringify(object);
const keypair = await this.userKeypairService.getUserKeypair(user.id);
@@ -169,7 +169,7 @@ export class ApRequestService {
* @param url URL to fetch
*/
@bindThis
- public async signedGet(url: string, user: { id: User['id'] }) {
+ public async signedGet(url: string, user: { id: User['id'] }): Promise<unknown> {
const keypair = await this.userKeypairService.getUserKeypair(user.id);
const req = ApRequestCreator.createSignedGet({
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index d3e0345c9c..aa4720c56e 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -61,10 +61,6 @@ export class Resolver {
@bindThis
public async resolve(value: string | IObject): Promise<IObject> {
- if (value == null) {
- throw new Error('resolvee is null (or undefined)');
- }
-
if (typeof value !== 'string') {
return value;
}
@@ -104,11 +100,11 @@ export class Resolver {
? await this.apRequestService.signedGet(value, this.user) as IObject
: await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject;
- if (object == null || (
+ if (
Array.isArray(object['@context']) ?
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
- )) {
+ ) {
throw new Error('invalid response');
}
diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts
index 20fe2a0a77..af7e243229 100644
--- a/packages/backend/src/core/activitypub/LdSignatureService.ts
+++ b/packages/backend/src/core/activitypub/LdSignatureService.ts
@@ -3,6 +3,8 @@ import { Injectable } from '@nestjs/common';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
import { CONTEXTS } from './misc/contexts.js';
+import type { JsonLdDocument } from 'jsonld';
+import type { JsonLd, RemoteDocument } from 'jsonld/jsonld-spec.js';
// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017
@@ -18,22 +20,21 @@ class LdSignature {
@bindThis
public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise<any> {
- const options = {
- type: 'RsaSignature2017',
- creator,
- domain,
- nonce: crypto.randomBytes(16).toString('hex'),
- created: (created ?? new Date()).toISOString(),
- } as {
+ const options: {
type: string;
creator: string;
domain?: string;
nonce: string;
created: string;
+ } = {
+ type: 'RsaSignature2017',
+ creator,
+ nonce: crypto.randomBytes(16).toString('hex'),
+ created: (created ?? new Date()).toISOString(),
};
- if (!domain) {
- delete options.domain;
+ if (domain) {
+ options.domain = domain;
}
const toBeSigned = await this.createVerifyData(data, options);
@@ -62,7 +63,7 @@ class LdSignature {
}
@bindThis
- public async createVerifyData(data: any, options: any) {
+ public async createVerifyData(data: any, options: any): Promise<string> {
const transformedOptions = {
...options,
'@context': 'https://w3id.org/identity/v1',
@@ -82,7 +83,7 @@ class LdSignature {
}
@bindThis
- public async normalize(data: any) {
+ public async normalize(data: JsonLdDocument): Promise<string> {
const customLoader = this.getLoader();
// XXX: Importing jsonld dynamically since Jest frequently fails to import it statically
// https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595
@@ -93,14 +94,14 @@ class LdSignature {
@bindThis
private getLoader() {
- return async (url: string): Promise<any> => {
- if (!url.match('^https?\:\/\/')) throw new Error(`Invalid URL ${url}`);
+ return async (url: string): Promise<RemoteDocument> => {
+ if (!/^https?:\/\//.test(url)) throw new Error(`Invalid URL ${url}`);
if (this.preLoad) {
if (url in CONTEXTS) {
if (this.debug) console.debug(`HIT: ${url}`);
return {
- contextUrl: null,
+ contextUrl: undefined,
document: CONTEXTS[url],
documentUrl: url,
};
@@ -110,7 +111,7 @@ class LdSignature {
if (this.debug) console.debug(`MISS: ${url}`);
const document = await this.fetchDocument(url);
return {
- contextUrl: null,
+ contextUrl: undefined,
document: document,
documentUrl: url,
};
@@ -118,13 +119,17 @@ class LdSignature {
}
@bindThis
- private async fetchDocument(url: string) {
- const json = await this.httpRequestService.send(url, {
- headers: {
- Accept: 'application/ld+json, application/json',
+ private async fetchDocument(url: string): Promise<JsonLd> {
+ const json = await this.httpRequestService.send(
+ url,
+ {
+ headers: {
+ Accept: 'application/ld+json, application/json',
+ },
+ timeout: this.loderTimeout,
},
- timeout: this.loderTimeout,
- }, { throwErrorWhenResponseNotOk: false }).then(res => {
+ { throwErrorWhenResponseNotOk: false },
+ ).then(res => {
if (!res.ok) {
throw new Error(`${res.status} ${res.statusText}`);
} else {
@@ -132,7 +137,7 @@ class LdSignature {
}
});
- return json;
+ return json as JsonLd;
}
@bindThis
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index aee0d3629c..2bcd3811b2 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -1,3 +1,5 @@
+import type { JsonLd } from 'jsonld/jsonld-spec.js';
+
/* eslint:disable:quotemark indent */
const id_v1 = {
'@context': {
@@ -86,7 +88,7 @@ const id_v1 = {
'accessControl': { '@id': 'perm:accessControl', '@type': '@id' },
'writePermission': { '@id': 'perm:writePermission', '@type': '@id' },
},
-};
+} satisfies JsonLd;
const security_v1 = {
'@context': {
@@ -137,7 +139,7 @@ const security_v1 = {
'signatureAlgorithm': 'sec:signingAlgorithm',
'signatureValue': 'sec:signatureValue',
},
-};
+} satisfies JsonLd;
const activitystreams = {
'@context': {
@@ -517,9 +519,9 @@ const activitystreams = {
'@type': '@id',
},
},
-};
+} satisfies JsonLd;
-export const CONTEXTS: Record<string, unknown> = {
+export const CONTEXTS: Record<string, JsonLd> = {
'https://w3id.org/identity/v1': id_v1,
'https://w3id.org/security/v1': security_v1,
'https://www.w3.org/ns/activitystreams': activitystreams,
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index d3359ef900..46ed976a6b 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -200,7 +200,7 @@ export class ApNoteService {
| { status: 'ok'; res: Note }
| { status: 'permerror' | 'temperror' }
> => {
- if (!uri.match(/^https?:/)) return { status: 'permerror' };
+ if (!/^https?:/.test(uri)) return { status: 'permerror' };
try {
const res = await this.resolveNote(uri);
if (res == null) return { status: 'permerror' };
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 625135da6c..4bb0fa61ec 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -194,7 +194,6 @@ export interface IApPropertyValue extends IObject {
}
export const isPropertyValue = (object: IObject): object is IApPropertyValue =>
- object &&
getApType(object) === 'PropertyValue' &&
typeof object.name === 'string' &&
'value' in object &&
diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index 15ffd44861..d24657260f 100644
--- a/packages/backend/src/core/entities/ChannelEntityService.ts
+++ b/packages/backend/src/core/entities/ChannelEntityService.ts
@@ -47,17 +47,26 @@ export class ChannelEntityService {
const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
- const hasUnreadNote = meId ? (await this.noteUnreadsRepository.findOneBy({ noteChannelId: channel.id, userId: meId })) != null : undefined;
+ const hasUnreadNote = meId ? await this.noteUnreadsRepository.exist({
+ where: {
+ noteChannelId: channel.id,
+ userId: meId
+ },
+ }) : undefined;
- const following = meId ? await this.channelFollowingsRepository.findOneBy({
- followerId: meId,
- followeeId: channel.id,
- }) : null;
+ const isFollowing = meId ? await this.channelFollowingsRepository.exist({
+ where: {
+ followerId: meId,
+ followeeId: channel.id,
+ },
+ }) : false;
- const favorite = meId ? await this.channelFavoritesRepository.findOneBy({
- userId: meId,
- channelId: channel.id,
- }) : null;
+ const isFavorited = meId ? await this.channelFavoritesRepository.exist({
+ where: {
+ userId: meId,
+ channelId: channel.id,
+ },
+ }) : false;
const pinnedNotes = channel.pinnedNoteIds.length > 0 ? await this.notesRepository.find({
where: {
@@ -80,8 +89,8 @@ export class ChannelEntityService {
notesCount: channel.notesCount,
...(me ? {
- isFollowing: following != null,
- isFavorited: favorite != null,
+ isFollowing,
+ isFavorited,
hasUnreadNote,
} : {}),
diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts
index 33d3c53806..f558cbc33d 100644
--- a/packages/backend/src/core/entities/ClipEntityService.ts
+++ b/packages/backend/src/core/entities/ClipEntityService.ts
@@ -39,7 +39,7 @@ export class ClipEntityService {
description: clip.description,
isPublic: clip.isPublic,
favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
- isFavorited: meId ? await this.clipFavoritesRepository.findOneBy({ clipId: clip.id, userId: meId }).then(x => x != null) : undefined,
+ isFavorited: meId ? await this.clipFavoritesRepository.exist({ where: { clipId: clip.id, userId: meId } }) : undefined,
});
}
diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
index e52a591884..92345457c9 100644
--- a/packages/backend/src/core/entities/FlashEntityService.ts
+++ b/packages/backend/src/core/entities/FlashEntityService.ts
@@ -40,7 +40,7 @@ export class FlashEntityService {
summary: flash.summary,
script: flash.script,
likedCount: flash.likedCount,
- isLiked: meId ? await this.flashLikesRepository.findOneBy({ flashId: flash.id, userId: meId }).then(x => x != null) : undefined,
+ isLiked: meId ? await this.flashLikesRepository.exist({ where: { flashId: flash.id, userId: meId } }) : undefined,
});
}
diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts
index 632c75304f..c44a5df118 100644
--- a/packages/backend/src/core/entities/GalleryPostEntityService.ts
+++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts
@@ -46,7 +46,7 @@ export class GalleryPostEntityService {
tags: post.tags.length > 0 ? post.tags : undefined,
isSensitive: post.isSensitive,
likedCount: post.likedCount,
- isLiked: meId ? await this.galleryLikesRepository.findOneBy({ postId: post.id, userId: meId }).then(x => x != null) : undefined,
+ isLiked: meId ? await this.galleryLikesRepository.exist({ where: { postId: post.id, userId: meId } }) : undefined,
});
}
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index e730c0eb8f..546e5f56d2 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -106,16 +106,14 @@ export class NoteEntityService implements OnModuleInit {
hide = false;
} else {
// フォロワーかどうか
- const following = await this.followingsRepository.findOneBy({
- followeeId: packedNote.userId,
- followerId: meId,
+ const isFollowing = await this.followingsRepository.exist({
+ where: {
+ followeeId: packedNote.userId,
+ followerId: meId,
+ },
});
- if (following == null) {
- hide = true;
- } else {
- hide = false;
- }
+ hide = !isFollowing;
}
}
diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts
index d6da856637..94b26a5017 100644
--- a/packages/backend/src/core/entities/PageEntityService.ts
+++ b/packages/backend/src/core/entities/PageEntityService.ts
@@ -97,7 +97,7 @@ export class PageEntityService {
eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null,
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)),
likedCount: page.likedCount,
- isLiked: meId ? await this.pageLikesRepository.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
+ isLiked: meId ? await this.pageLikesRepository.exist({ where: { pageId: page.id, userId: meId } }) : undefined,
});
}
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 6dacaa1032..7d248f8524 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -230,12 +230,14 @@ export class UserEntityService implements OnModuleInit {
/*
const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
- const unread = myAntennas.length > 0 ? await this.antennaNotesRepository.findOneBy({
- antennaId: In(myAntennas.map(x => x.id)),
- read: false,
- }) : null;
+ const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exist({
+ where: {
+ antennaId: In(myAntennas.map(x => x.id)),
+ read: false,
+ },
+ }) : false);
- return unread != null;
+ return isUnread;
*/
return false; // TODO
}
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index f53351d999..f751709345 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -189,7 +189,11 @@ export class ActivityPubServerService {
return (this.apRendererService.addContext(rendered));
} else {
// index page
- const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`);
+ const rendered = this.apRendererService.renderOrderedCollection(
+ partOf,
+ user.followersCount,
+ `${partOf}?page=true`,
+ );
reply.header('Cache-Control', 'public, max-age=180');
this.setResponseType(request, reply);
return (this.apRendererService.addContext(rendered));
@@ -277,7 +281,11 @@ export class ActivityPubServerService {
return (this.apRendererService.addContext(rendered));
} else {
// index page
- const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`);
+ const rendered = this.apRendererService.renderOrderedCollection(
+ partOf,
+ user.followingCount,
+ `${partOf}?page=true`,
+ );
reply.header('Cache-Control', 'public, max-age=180');
this.setResponseType(request, reply);
return (this.apRendererService.addContext(rendered));
@@ -310,7 +318,10 @@ export class ActivityPubServerService {
const rendered = this.apRendererService.renderOrderedCollection(
`${this.config.url}/users/${userId}/collections/featured`,
- renderedNotes.length, undefined, undefined, renderedNotes,
+ renderedNotes.length,
+ undefined,
+ undefined,
+ renderedNotes,
);
reply.header('Cache-Control', 'public, max-age=180');
@@ -395,7 +406,9 @@ export class ActivityPubServerService {
return (this.apRendererService.addContext(rendered));
} else {
// index page
- const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount,
+ const rendered = this.apRendererService.renderOrderedCollection(
+ partOf,
+ user.notesCount,
`${partOf}?page=true`,
`${partOf}?page=true&since_id=000000000000000000000000`,
);
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index fc5f3811eb..5e18dcbe08 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -128,12 +128,12 @@ export class SignupApiService {
}
if (instance.emailRequiredForSignup) {
- if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) {
+ if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
}
// Check deleted username duplication
- if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) {
+ if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
throw new FastifyReplyError(400, 'USED_USERNAME');
}
diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
index bee1ffbaee..8401cf51d9 100644
--- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
@@ -50,9 +50,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw e;
});
- const exist = await this.promoNotesRepository.findOneBy({ noteId: note.id });
+ const exist = await this.promoNotesRepository.exist({ where: { noteId: note.id } });
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyPromoted);
}
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
index 467f157a61..1fedab4540 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
@@ -69,8 +69,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps) => {
- const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
- if (role == null) {
+ const roleExist = await this.rolesRepository.exist({ where: { id: ps.roleId } });
+ if (!roleExist) {
throw new ApiError(meta.errors.noSuchRole);
}
diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts
index e69f9c12e2..aa199ab730 100644
--- a/packages/backend/src/server/api/endpoints/auth/accept.ts
+++ b/packages/backend/src/server/api/endpoints/auth/accept.ts
@@ -58,12 +58,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const accessToken = secureRndstr(32);
// Fetch exist access token
- const exist = await this.accessTokensRepository.findOneBy({
- appId: session.appId,
- userId: me.id,
+ const exist = await this.accessTokensRepository.exist({
+ where: {
+ appId: session.appId,
+ userId: me.id,
+ },
});
- if (exist == null) {
+ if (!exist) {
const app = await this.appsRepository.findOneByOrFail({ id: session.appId });
// Generate Hash
diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts
index d9ba99f209..4ad40c8f1c 100644
--- a/packages/backend/src/server/api/endpoints/blocking/create.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/create.ts
@@ -84,12 +84,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
// Check if already blocking
- const exist = await this.blockingsRepository.findOneBy({
- blockerId: blocker.id,
- blockeeId: blockee.id,
+ const exist = await this.blockingsRepository.exist({
+ where: {
+ blockerId: blocker.id,
+ blockeeId: blockee.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyBlocking);
}
diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts
index 46dd26a45a..38913ae932 100644
--- a/packages/backend/src/server/api/endpoints/blocking/delete.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts
@@ -84,12 +84,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
// Check not blocking
- const exist = await this.blockingsRepository.findOneBy({
- blockerId: blocker.id,
- blockeeId: blockee.id,
+ const exist = await this.blockingsRepository.exist({
+ where: {
+ blockerId: blocker.id,
+ blockeeId: blockee.id,
+ }
});
- if (exist == null) {
+ if (!exist) {
throw new ApiError(meta.errors.notBlocking);
}
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index c3561e2a71..2837f2cf81 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -87,12 +87,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw e;
});
- const exist = await this.clipNotesRepository.findOneBy({
- noteId: note.id,
- clipId: clip.id,
+ const exist = await this.clipNotesRepository.exist({
+ where: {
+ noteId: note.id,
+ clipId: clip.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyClipped);
}
diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts
index f08caaf8d7..ce09855531 100644
--- a/packages/backend/src/server/api/endpoints/clips/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts
@@ -58,12 +58,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchClip);
}
- const exist = await this.clipFavoritesRepository.findOneBy({
- clipId: clip.id,
- userId: me.id,
+ const exist = await this.clipFavoritesRepository.exist({
+ where: {
+ clipId: clip.id,
+ userId: me.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyFavorited);
}
diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
index 290cd4d2ce..cdcdde7e8a 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
@@ -34,12 +34,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private driveFilesRepository: DriveFilesRepository,
) {
super(meta, paramDef, async (ps, me) => {
- const file = await this.driveFilesRepository.findOneBy({
- md5: ps.md5,
- userId: me.id,
+ const exist = await this.driveFilesRepository.exist({
+ where: {
+ md5: ps.md5,
+ userId: me.id,
+ },
});
- return file != null;
+ return exist;
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts
index 23de2f3970..57245f9f41 100644
--- a/packages/backend/src/server/api/endpoints/flash/like.ts
+++ b/packages/backend/src/server/api/endpoints/flash/like.ts
@@ -66,12 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
// if already liked
- const exist = await this.flashLikesRepository.findOneBy({
- flashId: flash.id,
- userId: me.id,
+ const exist = await this.flashLikesRepository.exist({
+ where: {
+ flashId: flash.id,
+ userId: me.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyLiked);
}
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index 4ad16de911..009fc96f64 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -99,12 +99,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
// Check if already following
- const exist = await this.followingsRepository.findOneBy({
- followerId: follower.id,
- followeeId: followee.id,
+ const exist = await this.followingsRepository.exist({
+ where: {
+ followerId: follower.id,
+ followeeId: followee.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyFollowing);
}
diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts
index 4f12db1273..570c4eb81e 100644
--- a/packages/backend/src/server/api/endpoints/following/delete.ts
+++ b/packages/backend/src/server/api/endpoints/following/delete.ts
@@ -84,12 +84,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
// Check not following
- const exist = await this.followingsRepository.findOneBy({
- followerId: follower.id,
- followeeId: followee.id,
+ const exist = await this.followingsRepository.exist({
+ where: {
+ followerId: follower.id,
+ followeeId: followee.id,
+ },
});
- if (exist == null) {
+ if (!exist) {
throw new ApiError(meta.errors.notFollowing);
}
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
index 6ac5fa8606..c0bb55f640 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
@@ -66,12 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
// if already liked
- const exist = await this.galleryLikesRepository.findOneBy({
- postId: post.id,
- userId: me.id,
+ const exist = await this.galleryLikesRepository.exist({
+ where: {
+ postId: post.id,
+ userId: me.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyLiked);
}
diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
index 12ec5855d3..8582e98f76 100644
--- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
@@ -66,8 +66,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private downloadService: DownloadService,
) {
super(meta, paramDef, async (ps, me) => {
- const users = await this.usersRepository.findOneBy({ id: me.id });
- if (users === null) throw new ApiError(meta.errors.noSuchUser);
+ const userExist = await this.usersRepository.exist({ where: { id: me.id } });
+ if (!userExist) throw new ApiError(meta.errors.noSuchUser);
const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
if (file === null) throw new ApiError(meta.errors.noSuchFile);
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
index b8922b91e5..352fe54c5d 100644
--- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts
+++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
@@ -47,19 +47,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
) {
super(meta, paramDef, async (ps, me) => {
// Check if announcement exists
- const announcement = await this.announcementsRepository.findOneBy({ id: ps.announcementId });
+ const announcementExist = await this.announcementsRepository.exist({ where: { id: ps.announcementId } });
- if (announcement == null) {
+ if (!announcementExist) {
throw new ApiError(meta.errors.noSuchAnnouncement);
}
// Check if already read
- const read = await this.announcementReadsRepository.findOneBy({
- announcementId: ps.announcementId,
- userId: me.id,
+ const alreadyRead = await this.announcementReadsRepository.exist({
+ where: {
+ announcementId: ps.announcementId,
+ userId: me.id,
+ },
});
- if (read != null) {
+ if (alreadyRead) {
return;
}
diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
index 93daeb0cd7..415a60147b 100644
--- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
@@ -28,9 +28,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
- const token = await this.accessTokensRepository.findOneBy({ id: ps.tokenId });
+ const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } });
- if (token) {
+ if (tokenExist) {
await this.accessTokensRepository.delete({
id: ps.tokenId,
userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index ee358d5c6c..ef53f9ef41 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -79,12 +79,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
// Check if already muting
- const exist = await this.mutingsRepository.findOneBy({
- muterId: muter.id,
- muteeId: mutee.id,
+ const exist = await this.mutingsRepository.exist({
+ where: {
+ muterId: muter.id,
+ muteeId: mutee.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyMuting);
}
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 96be5ed844..739316997a 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -217,11 +217,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
// Check blocking
if (renote.userId !== me.id) {
- const block = await this.blockingsRepository.findOneBy({
- blockerId: renote.userId,
- blockeeId: me.id,
+ const blockExist = await this.blockingsRepository.exist({
+ where: {
+ blockerId: renote.userId,
+ blockeeId: me.id,
+ },
});
- if (block) {
+ if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
@@ -240,11 +242,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
// Check blocking
if (reply.userId !== me.id) {
- const block = await this.blockingsRepository.findOneBy({
- blockerId: reply.userId,
- blockeeId: me.id,
+ const blockExist = await this.blockingsRepository.exist({
+ where: {
+ blockerId: reply.userId,
+ blockeeId: me.id,
+ },
});
- if (block) {
+ if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index 611ea19560..9299d66039 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -63,12 +63,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
// if already favorited
- const exist = await this.noteFavoritesRepository.findOneBy({
- noteId: note.id,
- userId: me.id,
+ const exist = await this.noteFavoritesRepository.exist({
+ where: {
+ noteId: note.id,
+ userId: me.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyFavorited);
}
diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts
index 543c126d9c..bc66488103 100644
--- a/packages/backend/src/server/api/endpoints/pages/like.ts
+++ b/packages/backend/src/server/api/endpoints/pages/like.ts
@@ -66,12 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
// if already liked
- const exist = await this.pageLikesRepository.findOneBy({
- pageId: page.id,
- userId: me.id,
+ const exist = await this.pageLikesRepository.exist({
+ where: {
+ pageId: page.id,
+ userId: me.id,
+ },
});
- if (exist != null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyLiked);
}
diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts
index 90febdbce7..9baa930f5f 100644
--- a/packages/backend/src/server/api/endpoints/promo/read.ts
+++ b/packages/backend/src/server/api/endpoints/promo/read.ts
@@ -44,12 +44,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw err;
});
- const exist = await this.promoReadsRepository.findOneBy({
- noteId: note.id,
- userId: me.id,
+ const exist = await this.promoReadsRepository.exist({
+ where: {
+ noteId: note.id,
+ userId: me.id,
+ },
});
- if (exist != null) {
+ if (exist) {
return;
}
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index c6f71474bc..18d66500ab 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -97,11 +97,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (me == null) {
throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) {
- const following = await this.followingsRepository.findOneBy({
- followeeId: user.id,
- followerId: me.id,
+ const isFollowing = await this.followingsRepository.exist({
+ where: {
+ followeeId: user.id,
+ followerId: me.id,
+ },
});
- if (following == null) {
+ if (!isFollowing) {
throw new ApiError(meta.errors.forbidden);
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index b072c96626..6ea7b923d6 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -97,11 +97,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (me == null) {
throw new ApiError(meta.errors.forbidden);
} else if (me.id !== user.id) {
- const following = await this.followingsRepository.findOneBy({
- followeeId: user.id,
- followerId: me.id,
+ const isFollowing = await this.followingsRepository.exist({
+ where: {
+ followeeId: user.id,
+ followerId: me.id,
+ },
});
- if (following == null) {
+ if (!isFollowing) {
throw new ApiError(meta.errors.forbidden);
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
index cccffcdad4..beb0ba85ff 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
@@ -84,11 +84,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
- const list = await this.userListsRepository.findOneBy({
- id: ps.listId,
- isPublic: true,
+ const listExist = await this.userListsRepository.exist({
+ where: {
+ id: ps.listId,
+ isPublic: true,
+ },
});
- if (list === null) throw new ApiError(meta.errors.noSuchList);
+ if (!listExist) throw new ApiError(meta.errors.noSuchList);
const currentCount = await this.userListsRepository.countBy({
userId: me.id,
});
@@ -114,18 +116,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
if (currentUser.id !== me.id) {
- const block = await this.blockingsRepository.findOneBy({
- blockerId: currentUser.id,
- blockeeId: me.id,
+ const blockExist = await this.blockingsRepository.exist({
+ where: {
+ blockerId: currentUser.id,
+ blockeeId: me.id,
+ },
});
- if (block) {
+ if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
- const exist = await this.userListJoiningsRepository.findOneBy({
- userListId: userList.id,
- userId: currentUser.id,
+ const exist = await this.userListJoiningsRepository.exist({
+ where: {
+ userListId: userList.id,
+ userId: currentUser.id,
+ },
});
if (exist) {
diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
index ea1a022bec..2c09a47fef 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
@@ -41,21 +41,25 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
- const userList = await this.userListsRepository.findOneBy({
- id: ps.listId,
- isPublic: true,
+ const userListExist = await this.userListsRepository.exist({
+ where: {
+ id: ps.listId,
+ isPublic: true,
+ },
});
- if (userList === null) {
+ if (!userListExist) {
throw new ApiError(meta.errors.noSuchList);
}
- const exist = await this.userListFavoritesRepository.findOneBy({
- userId: me.id,
- userListId: ps.listId,
+ const exist = await this.userListFavoritesRepository.exist({
+ where: {
+ userId: me.id,
+ userListId: ps.listId,
+ },
});
- if (exist !== null) {
+ if (exist) {
throw new ApiError(meta.errors.alreadyFavorited);
}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index 925037e484..6e1f6b2c62 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -100,18 +100,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
// Check blocking
if (user.id !== me.id) {
- const block = await this.blockingsRepository.findOneBy({
- blockerId: user.id,
- blockeeId: me.id,
+ const blockExist = await this.blockingsRepository.exist({
+ where: {
+ blockerId: user.id,
+ blockeeId: me.id,
+ },
});
- if (block) {
+ if (blockExist) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
}
- const exist = await this.userListJoiningsRepository.findOneBy({
- userListId: userList.id,
- userId: user.id,
+ const exist = await this.userListJoiningsRepository.exist({
+ where: {
+ userListId: userList.id,
+ userId: user.id,
+ },
});
if (exist) {
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index 8077841c8c..3fd418d04e 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -69,10 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
userListId: ps.listId,
});
if (me !== null) {
- additionalProperties.isLiked = (await this.userListFavoritesRepository.findOneBy({
- userId: me.id,
- userListId: ps.listId,
- }) !== null);
+ additionalProperties.isLiked = await this.userListFavoritesRepository.exist({
+ where: {
+ userId: me.id,
+ userListId: ps.listId,
+ },
+ });
} else {
additionalProperties.isLiked = false;
}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
index be8e317816..a7c3b58947 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
@@ -39,12 +39,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private userListFavoritesRepository: UserListFavoritesRepository,
) {
super(meta, paramDef, async (ps, me) => {
- const userList = await this.userListsRepository.findOneBy({
- id: ps.listId,
- isPublic: true,
+ const userListExist = await this.userListsRepository.exist({
+ where: {
+ id: ps.listId,
+ isPublic: true,
+ },
});
- if (userList === null) {
+ if (!userListExist) {
throw new ApiError(meta.errors.noSuchList);
}
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index d97d2b8ba9..ea4cff0bc0 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -34,11 +34,13 @@ class UserListChannel extends Channel {
this.listId = params.listId as string;
// Check existence and owner
- const list = await this.userListsRepository.findOneBy({
- id: this.listId,
- userId: this.user!.id,
+ const listExist = await this.userListsRepository.exist({
+ where: {
+ id: this.listId,
+ userId: this.user!.id,
+ },
});
- if (!list) return;
+ if (!listExist) return;
// Subscribe stream
this.subscriber.on(`userListStream:${this.listId}`, this.send);
diff --git a/packages/frontend/.storybook/changes.ts b/packages/frontend/.storybook/changes.ts
index fc0f0c286b..a1275132be 100644
--- a/packages/frontend/.storybook/changes.ts
+++ b/packages/frontend/.storybook/changes.ts
@@ -1,7 +1,10 @@
import fs from 'node:fs/promises';
+import { fileURLToPath } from 'node:url';
import path from 'node:path';
import micromatch from 'micromatch';
-import main from './main';
+import main from './main.js';
+
+const __dirname = fileURLToPath(new URL('.', import.meta.url));
interface Stats {
readonly modules: readonly {
@@ -13,8 +16,8 @@ interface Stats {
}[];
}
-fs.readFile(
- path.resolve(__dirname, '../storybook-static/preview-stats.json')
+await fs.readFile(
+ new URL('../storybook-static/preview-stats.json', import.meta.url)
).then((buffer) => {
const stats: Stats = JSON.parse(buffer.toString());
const keys = new Set(stats.modules.map((stat) => stat.id));
diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts
index 1d0ce5ab63..b64979980a 100644
--- a/packages/frontend/.storybook/main.ts
+++ b/packages/frontend/.storybook/main.ts
@@ -1,7 +1,11 @@
import { resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
import type { StorybookConfig } from '@storybook/vue3-vite';
import { type Plugin, mergeConfig } from 'vite';
import turbosnap from 'vite-plugin-turbosnap';
+
+const dirname = fileURLToPath(new URL('.', import.meta.url));
+
const config = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
@@ -9,7 +13,7 @@ const config = {
'@storybook/addon-interactions',
'@storybook/addon-links',
'@storybook/addon-storysource',
- resolve(__dirname, '../node_modules/storybook-addon-misskey-theme'),
+ resolve(dirname, '../node_modules/storybook-addon-misskey-theme'),
],
framework: {
name: '@storybook/vue3-vite',
@@ -28,7 +32,8 @@ const config = {
}
return mergeConfig(config, {
plugins: [
- turbosnap({
+ // XXX: https://github.com/IanVS/vite-plugin-turbosnap/issues/8
+ (turbosnap as any as typeof turbosnap['default'])({
rootDir: config.root ?? process.cwd(),
}),
],
diff --git a/packages/frontend/.storybook/package.json b/packages/frontend/.storybook/package.json
new file mode 100644
index 0000000000..bedb411a91
--- /dev/null
+++ b/packages/frontend/.storybook/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/packages/frontend/.storybook/preload-locale.ts b/packages/frontend/.storybook/preload-locale.ts
index a54164742a..2b7362b88d 100644
--- a/packages/frontend/.storybook/preload-locale.ts
+++ b/packages/frontend/.storybook/preload-locale.ts
@@ -1,9 +1,8 @@
import { writeFile } from 'node:fs/promises';
-import { resolve } from 'node:path';
-import * as locales from '../../../locales';
+import locales from '../../../locales/index.js';
-writeFile(
- resolve(__dirname, 'locale.ts'),
+await writeFile(
+ new URL('locale.ts', import.meta.url),
`export default ${JSON.stringify(locales['ja-JP'], undefined, 2)} as const;`,
'utf8',
)
diff --git a/packages/frontend/.storybook/preload-theme.ts b/packages/frontend/.storybook/preload-theme.ts
index 1ff8f71ecd..42fbeff738 100644
--- a/packages/frontend/.storybook/preload-theme.ts
+++ b/packages/frontend/.storybook/preload-theme.ts
@@ -1,6 +1,5 @@
import { readFile, writeFile } from 'node:fs/promises';
-import { resolve } from 'node:path';
-import * as JSON5 from 'json5';
+import JSON5 from 'json5';
const keys = [
'_dark',
@@ -26,9 +25,9 @@ const keys = [
'd-u0',
]
-Promise.all(keys.map((key) => readFile(resolve(__dirname, `../src/themes/${key}.json5`), 'utf8'))).then((sources) => {
+await Promise.all(keys.map((key) => readFile(new URL(`../src/themes/${key}.json5`, import.meta.url), 'utf8'))).then((sources) => {
writeFile(
- resolve(__dirname, './themes.ts'),
+ new URL('./themes.ts', import.meta.url),
`export default ${JSON.stringify(
Object.fromEntries(sources.map((source, i) => [keys[i], JSON5.parse(source)])),
undefined,
diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts
index e887acaa2e..67c81c666b 100644
--- a/packages/frontend/.storybook/preview.ts
+++ b/packages/frontend/.storybook/preview.ts
@@ -3,10 +3,10 @@ import { FORCE_REMOUNT } from '@storybook/core-events';
import { type Preview, setup } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import { initialize, mswDecorator } from 'msw-storybook-addon';
-import { userDetailed } from './fakes';
-import locale from './locale';
-import { commonHandlers, onUnhandledRequest } from './mocks';
-import themes from './themes';
+import { userDetailed } from './fakes.js';
+import locale from './locale.js';
+import { commonHandlers, onUnhandledRequest } from './mocks.js';
+import themes from './themes.js';
import '../src/style.scss';
const appInitialized = Symbol();
diff --git a/packages/frontend/.storybook/tsconfig.json b/packages/frontend/.storybook/tsconfig.json
index 2db2f1eabe..02465f5afa 100644
--- a/packages/frontend/.storybook/tsconfig.json
+++ b/packages/frontend/.storybook/tsconfig.json
@@ -1,5 +1,7 @@
{
"compilerOptions": {
+ "target": "es2022",
+ "module": "Node16",
"strict": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false,
diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts
index c95da64bba..cb315c8ff7 100644
--- a/packages/frontend/src/cache.ts
+++ b/packages/frontend/src/cache.ts
@@ -4,3 +4,4 @@ import { Cache } from '@/scripts/cache';
export const clipsCache = new Cache<misskey.entities.Clip[]>(Infinity);
export const rolesCache = new Cache(Infinity);
export const userListsCache = new Cache<misskey.entities.UserList[]>(Infinity);
+export const antennasCache = new Cache<misskey.entities.Antenna[]>(Infinity);
diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue
index 630620fc08..437dce0a14 100644
--- a/packages/frontend/src/components/MkAvatars.vue
+++ b/packages/frontend/src/components/MkAvatars.vue
@@ -1,24 +1,29 @@
<template>
<div>
- <div v-for="user in users" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;">
+ <div v-for="user in users.slice(0, limit)" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;">
<MkAvatar :user="user" style="width:32px; height:32px;" indicator link preview/>
</div>
+ <div v-if="users.length > limit" style="display: inline-block;">...</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as os from '@/os';
+import { UserLite } from 'misskey-js/built/entities';
-const props = defineProps<{
+const props = withDefaults(defineProps<{
userIds: string[];
-}>();
+ limit?: number;
+}>(), {
+ limit: Infinity,
+});
-const users = ref([]);
+const users = ref<UserLite[]>([]);
onMounted(async () => {
users.value = await os.api('users/show', {
userIds: props.userIds,
- });
+ }) as unknown as UserLite[];
});
</script>
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 34f59c5573..be0aed6524 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -113,7 +113,7 @@ onMounted(() => {
right: 0,
},
imageClickAction: 'close',
- tapAction: 'toggle-controls',
+ tapAction: 'close',
bgOpacity: 1,
showAnimationDuration: 100,
hideAnimationDuration: 100,
diff --git a/packages/frontend/src/components/global/MkA.stories.impl.ts b/packages/frontend/src/components/global/MkA.stories.impl.ts
index 639ed19af2..6e3ff573cb 100644
--- a/packages/frontend/src/components/global/MkA.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkA.stories.impl.ts
@@ -29,11 +29,11 @@ export const Default = {
const canvas = within(canvasElement);
const a = canvas.getByRole<HTMLAnchorElement>('link');
await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
- await userEvent.click(a, { button: 2 });
+ await userEvent.pointer({ keys: '[MouseRight]', target: a });
await tick();
const menu = canvas.getByRole('menu');
await expect(menu).toBeInTheDocument();
- await userEvent.click(a, { button: 0 });
+ await userEvent.click(a);
a.blur();
await tick();
await expect(menu).not.toBeInTheDocument();
diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts
index 7d8a42a03c..58b6cc14c4 100644
--- a/packages/frontend/src/components/global/MkAd.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest';
-import { userEvent, within } from '@storybook/testing-library';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
-import { i18n } from '@/i18n';
import MkAd from './MkAd.vue';
+import { i18n } from '@/i18n';
const common = {
render(args) {
return {
@@ -36,6 +36,7 @@ const common = {
const i = buttons[0];
await expect(i).toBeInTheDocument();
await userEvent.click(i);
+ await waitFor(() => expect(canvasElement).toHaveTextContent(i18n.ts._ad.back));
await expect(a).not.toBeInTheDocument();
await expect(i).not.toBeInTheDocument();
buttons = canvas.getAllByRole<HTMLButtonElement>('button');
@@ -49,6 +50,7 @@ const common = {
await expect(back).toBeInTheDocument();
await expect(back).toHaveTextContent(i18n.ts._ad.back);
await userEvent.click(back);
+ await waitFor(() => expect(canvas.queryByRole('img')).toBeTruthy());
if (reduce) {
await expect(reduce).not.toBeInTheDocument();
}
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index bcc0fc6860..2a056f21d4 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -87,7 +87,7 @@ const props = defineProps<{
channelId: string;
}>();
-let tab = $ref('timeline');
+let tab = $ref('overview');
let channel = $ref(null);
let favorited = $ref(false);
let searchQuery = $ref('');
@@ -107,6 +107,9 @@ watch(() => props.channelId, async () => {
channelId: props.channelId,
});
favorited = channel.isFavorited;
+ if (favorited || channel.isFollowing) {
+ tab = 'timeline';
+ }
}, { immediate: true });
function edit() {
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index dde4d67f18..359bbeadc3 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -18,7 +18,7 @@
<MkButton inline @click="setTagBulk">Set tag</MkButton>
<MkButton inline @click="addTagBulk">Add tag</MkButton>
<MkButton inline @click="removeTagBulk">Remove tag</MkButton>
- <MkButton inline @click="setLisenceBulk">Set Lisence</MkButton>
+ <MkButton inline @click="setLicenseBulk">Set License</MkButton>
<MkButton inline danger @click="delBulk">Delete</MkButton>
</div>
<MkPagination ref="emojisPaginationComponent" :pagination="pagination">
@@ -221,7 +221,7 @@ const setCategoryBulk = async () => {
emojisPaginationComponent.value.reload();
};
-const setLisenceBulk = async () => {
+const setLicenseBulk = async () => {
const { canceled, result } = await os.inputText({
title: 'License',
});
diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue
index 355d18fdb5..632c36bbf8 100644
--- a/packages/frontend/src/pages/my-antennas/create.vue
+++ b/packages/frontend/src/pages/my-antennas/create.vue
@@ -9,6 +9,7 @@ import XAntenna from './editor.vue';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { useRouter } from '@/router';
+import { antennasCache } from '@/cache';
const router = useRouter();
@@ -26,13 +27,10 @@ let draft = $ref({
});
function onAntennaCreated() {
+ antennasCache.delete();
router.push('/my/antennas');
}
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
definePageMetadata({
title: i18n.ts.manageAntennas,
icon: 'ti ti-antenna',
diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue
index da9b2de48f..3fb9690ac1 100644
--- a/packages/frontend/src/pages/my-antennas/edit.vue
+++ b/packages/frontend/src/pages/my-antennas/edit.vue
@@ -10,6 +10,7 @@ import * as os from '@/os';
import { i18n } from '@/i18n';
import { useRouter } from '@/router';
import { definePageMetadata } from '@/scripts/page-metadata';
+import { antennasCache } from '@/cache';
const router = useRouter();
@@ -20,6 +21,7 @@ const props = defineProps<{
}>();
function onAntennaUpdated() {
+ antennasCache.delete();
router.push('/my/antennas');
}
@@ -27,10 +29,6 @@ os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) =
antenna = antennaResponse;
});
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
definePageMetadata({
title: i18n.ts.manageAntennas,
icon: 'ti ti-antenna',
diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue
index 2ca026b9a1..1e9136f1fa 100644
--- a/packages/frontend/src/pages/my-antennas/index.vue
+++ b/packages/frontend/src/pages/my-antennas/index.vue
@@ -2,15 +2,20 @@
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700">
- <div class="ieepwinx">
- <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+ <div>
+ <div v-if="antennas.length === 0" class="empty">
+ <div class="_fullinfo">
+ <img :src="infoImageUrl" class="_ghost"/>
+ <div>{{ i18n.ts.nothing }}</div>
+ </div>
+ </div>
+
+ <MkButton :link="true" to="/my/antennas/create" primary :class="$style.add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
- <div class="">
- <MkPagination v-slot="{items}" ref="list" :pagination="pagination">
- <MkA v-for="antenna in items" :key="antenna.id" class="ljoevbzj" :to="`/my/antennas/${antenna.id}`">
- <div class="name">{{ antenna.name }}</div>
- </MkA>
- </MkPagination>
+ <div v-if="antennas.length > 0" class="_gaps">
+ <MkA v-for="antenna in antennas" :key="antenna.id" :class="$style.antenna" :to="`/my/antennas/${antenna.id}`">
+ <div class="name">{{ antenna.name }}</div>
+ </MkA>
</div>
</div>
</MkSpacer>
@@ -18,19 +23,31 @@
</template>
<script lang="ts" setup>
-import { } from 'vue';
-import MkPagination from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
+import { antennasCache } from '@/cache';
+import { api } from '@/os';
+import { onActivated } from 'vue';
+import { infoImageUrl } from '@/instance';
-const pagination = {
- endpoint: 'antennas/list' as const,
- noPaging: true,
- limit: 10,
-};
+const antennas = $computed(() => antennasCache.value.value ?? []);
+
+function fetch() {
+ antennasCache.fetch(() => api('antennas/list'));
+}
-const headerActions = $computed(() => []);
+fetch();
+
+const headerActions = $computed(() => [{
+ asFullButton: true,
+ icon: 'ti ti-refresh',
+ text: i18n.ts.reload,
+ handler: () => {
+ antennasCache.delete();
+ fetch();
+ },
+}]);
const headerTabs = $computed(() => []);
@@ -38,30 +55,30 @@ definePageMetadata({
title: i18n.ts.manageAntennas,
icon: 'ti ti-antenna',
});
-</script>
-
-<style lang="scss" scoped>
-.ieepwinx {
- > .add {
- margin: 0 auto 16px auto;
- }
+onActivated(() => {
+ antennasCache.fetch(() => api('antennas/list'));
+});
+</script>
- .ljoevbzj {
- display: block;
- padding: 16px;
- margin-bottom: 8px;
- border: solid 1px var(--divider);
- border-radius: 6px;
+<style lang="scss" module>
+.add {
+ margin: 0 auto 16px auto;
+}
- &:hover {
- border: solid 1px var(--accent);
- text-decoration: none;
- }
+.antenna {
+ display: block;
+ padding: 16px;
+ border: solid 1px var(--divider);
+ border-radius: 6px;
- > .name {
- font-weight: bold;
- }
+ &:hover {
+ border: solid 1px var(--accent);
+ text-decoration: none;
}
}
+
+.name {
+ font-weight: bold;
+}
</style>
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index cee241c489..0f59ca0b36 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -3,38 +3,43 @@
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700">
<div class="_gaps">
+ <div v-if="items.length === 0" class="empty">
+ <div class="_fullinfo">
+ <img :src="infoImageUrl" class="_ghost"/>
+ <div>{{ i18n.ts.nothing }}</div>
+ </div>
+ </div>
+
<MkButton primary rounded style="margin: 0 auto;" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.createList }}</MkButton>
- <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination">
- <div class="_gaps">
- <MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
- <div style="margin-bottom: 4px;">{{ list.name }}</div>
- <MkAvatars :userIds="list.userIds"/>
- </MkA>
- </div>
- </MkPagination>
+ <div v-if="items.length > 0" class="_gaps">
+ <MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
+ <div style="margin-bottom: 4px;">{{ list.name }}</div>
+ <MkAvatars :userIds="list.userIds" :limit="10"/>
+ </MkA>
+ </div>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
-import { } from 'vue';
-import MkPagination from '@/components/MkPagination.vue';
+import { onActivated } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkAvatars from '@/components/MkAvatars.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { userListsCache } from '@/cache';
+import { infoImageUrl } from '@/instance';
+
+const items = $computed(() => userListsCache.value.value ?? []);
-const pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>();
+function fetch() {
+ userListsCache.fetch(() => os.api('users/lists/list'));
+}
-const pagination = {
- endpoint: 'users/lists/list' as const,
- noPaging: true,
- limit: 10,
-};
+fetch();
async function create() {
const { canceled, result: name } = await os.inputText({
@@ -43,10 +48,18 @@ async function create() {
if (canceled) return;
await os.apiWithDialog('users/lists/create', { name: name });
userListsCache.delete();
- pagingComponent.reload();
+ fetch();
}
-const headerActions = $computed(() => []);
+const headerActions = $computed(() => [{
+ asFullButton: true,
+ icon: 'ti ti-refresh',
+ text: i18n.ts.reload,
+ handler: () => {
+ userListsCache.delete();
+ fetch();
+ },
+}]);
const headerTabs = $computed(() => []);
@@ -58,6 +71,10 @@ definePageMetadata({
handler: create,
},
});
+
+onActivated(() => {
+ fetch();
+});
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index b4f056d8a6..d53519e0d5 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -166,7 +166,7 @@ const menuDef = computed(() => [{
active: currentPage?.route.name === 'import-export',
}, {
icon: 'ti ti-plane',
- text: `${i18n.ts.accountMigration} (${i18n.ts.experimental})`,
+ text: `${i18n.ts.accountMigration}`,
to: '/settings/migration',
active: currentPage?.route.name === 'migration',
}, {
diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue
index 102bc68523..38e0d0abb2 100644
--- a/packages/frontend/src/pages/settings/migration.vue
+++ b/packages/frontend/src/pages/settings/migration.vue
@@ -1,8 +1,5 @@
<template>
<div class="_gaps_m">
- <FormInfo warn>
- {{ i18n.ts.thisIsExperimentalFeature }}
- </FormInfo>
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-plane-arrival"></i></template>
<template #label>{{ i18n.ts._accountMigration.moveFrom }}</template>
diff --git a/packages/frontend/src/scripts/cache.ts b/packages/frontend/src/scripts/cache.ts
index 858e5f03bf..a61d858353 100644
--- a/packages/frontend/src/scripts/cache.ts
+++ b/packages/frontend/src/scripts/cache.ts
@@ -1,7 +1,8 @@
+import { ref } from "vue";
export class Cache<T> {
private cachedAt: number | null = null;
- private value: T | undefined;
+ public value = ref<T | undefined>();
private lifetime: number;
constructor(lifetime: Cache<never>['lifetime']) {
@@ -10,21 +11,20 @@ export class Cache<T> {
public set(value: T): void {
this.cachedAt = Date.now();
- this.value = value;
+ this.value.value = value;
}
- public get(): T | undefined {
+ private get(): T | undefined {
if (this.cachedAt == null) return undefined;
if ((Date.now() - this.cachedAt) > this.lifetime) {
- this.value = undefined;
+ this.value.value = undefined;
this.cachedAt = null;
return undefined;
}
- return this.value;
+ return this.value.value;
}
public delete() {
- this.value = undefined;
this.cachedAt = null;
}
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index c884ed76cb..1c93d58b44 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -1,14 +1,15 @@
+import { toUnicode } from 'punycode';
import { defineAsyncComponent } from 'vue';
import * as misskey from 'misskey-js';
import { i18n } from '@/i18n';
import copyToClipboard from '@/scripts/copy-to-clipboard';
-import { host } from '@/config';
+import { host, url } from '@/config';
import * as os from '@/os';
import { defaultStore, userActions } from '@/store';
import { $i, iAmModerator } from '@/account';
import { mainRouter } from '@/router';
import { Router } from '@/nirax';
-import { rolesCache, userListsCache } from '@/cache';
+import { antennasCache, rolesCache, userListsCache } from '@/cache';
export function getUserMenu(user: misskey.entities.UserDetailed, router: Router = mainRouter) {
const meId = $i ? $i.id : null;
@@ -138,6 +139,13 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
copyToClipboard(`${user.host ?? host}/@${user.username}.atom`);
},
}, {
+ icon: 'ti ti-share',
+ text: i18n.ts.copyProfileUrl,
+ action: () => {
+ const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
+ copyToClipboard(`${url}/${canonical}`);
+ },
+ }, {
icon: 'ti ti-mail',
text: i18n.ts.sendMessage,
action: () => {
@@ -158,11 +166,39 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
return lists.map(list => ({
text: list.name,
- action: () => {
- os.apiWithDialog('users/lists/push', {
+ action: async () => {
+ await os.apiWithDialog('users/lists/push', {
listId: list.id,
userId: user.id,
});
+ userListsCache.delete();
+ },
+ }));
+ },
+ }, {
+ type: 'parent',
+ icon: 'ti ti-antenna',
+ text: i18n.ts.addToAntenna,
+ children: async () => {
+ const antennas = await antennasCache.fetch(() => os.api('antennas/list'));
+ const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
+ return antennas.filter((a) => a.src === 'users').map(antenna => ({
+ text: antenna.name,
+ action: async () => {
+ await os.apiWithDialog('antennas/update', {
+ antennaId: antenna.id,
+ name: antenna.name,
+ keywords: antenna.keywords,
+ excludeKeywords: antenna.excludeKeywords,
+ src: antenna.src,
+ userListId: antenna.userListId,
+ users: [...antenna.users, canonical],
+ caseSensitive: antenna.caseSensitive,
+ withReplies: antenna.withReplies,
+ withFile: antenna.withFile,
+ notify: antenna.notify,
+ });
+ antennasCache.delete();
},
}));
},
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index b4b80a4bbe..bd74db7c85 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -72,7 +72,7 @@ html {
}
&.useSystemFont {
- font-family: 'Hiragino Maru Gothic Pro', sans-serif;
+ font-family: system-ui;
}
}
diff --git a/packages/sw/.eslintrc.js b/packages/sw/.eslintrc.cjs
index b1fd6b5edc..b1fd6b5edc 100644
--- a/packages/sw/.eslintrc.js
+++ b/packages/sw/.eslintrc.cjs
diff --git a/packages/sw/build.js b/packages/sw/build.js
index ad16fb9497..e926197640 100644
--- a/packages/sw/build.js
+++ b/packages/sw/build.js
@@ -1,10 +1,13 @@
// @ts-check
-const esbuild = require('esbuild');
-const locales = require('../../locales');
-const meta = require('../../package.json');
+import { fileURLToPath } from 'node:url';
+import * as esbuild from 'esbuild';
+import locales from '../../locales/index.js';
+import meta from '../../package.json' assert { type: "json" };
const watch = process.argv[2]?.includes('watch');
+const __dirname = fileURLToPath(new URL('.', import.meta.url))
+
console.log('Starting SW building...');
/** @type {esbuild.BuildOptions} */
diff --git a/packages/sw/package.json b/packages/sw/package.json
index a2bead3dd4..a60f8230c2 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -19,5 +19,6 @@
"eslint": "8.44.0",
"eslint-plugin-import": "2.27.5",
"typescript": "5.1.6"
- }
+ },
+ "type": "module"
}
diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
index 2783f2bfb5..1f085c7392 100644
--- a/packages/sw/src/sw.ts
+++ b/packages/sw/src/sw.ts
@@ -21,6 +21,10 @@ globalThis.addEventListener('activate', ev => {
);
});
+function offlineContentHTML(): string {
+ return `<!doctype html>Offline. Service Worker @${_VERSION_} <button onclick="location.reload()">reload</button>`
+}
+
globalThis.addEventListener('fetch', ev => {
let isHTMLRequest = false;
if (ev.request.headers.get('sec-fetch-dest') === 'document') {
@@ -34,7 +38,14 @@ globalThis.addEventListener('fetch', ev => {
if (!isHTMLRequest) return;
ev.respondWith(
fetch(ev.request)
- .catch(() => new Response(`Offline. Service Worker @${_VERSION_}`, { status: 200 })),
+ .catch(() => {
+ return new Response(offlineContentHTML(), {
+ status: 200,
+ headers: {
+ 'content-type': 'text/html',
+ },
+ });
+ }),
);
});