From 852c5d40350311251d5abd7e25bab6d8d53c4211 Mon Sep 17 00:00:00 2001 From: mattyatea Date: Sun, 28 Jan 2024 07:56:32 +0900 Subject: [PATCH] =?UTF-8?q?=E3=81=84=E3=82=8D=E3=81=84=E3=82=8D=E3=81=8B?= =?UTF-8?q?=E3=81=88=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 20 + locales/ja-JP.yml | 5 + packages/backend/src/core/RoleService.ts | 6 + .../backend/src/server/api/EndpointsModule.ts | 4 + packages/backend/src/server/api/endpoints.ts | 2 + .../api/endpoints/notes/user-list-timeline.ts | 2 +- .../endpoints/users/lists/list-favorite.ts | 85 ++++ .../server/api/endpoints/users/lists/list.ts | 38 +- packages/frontend/src/cache.ts | 1 + packages/frontend/src/const.ts | 6 +- packages/frontend/src/pages/admin/roles.vue | 401 ++++++++++-------- packages/frontend/src/pages/list.vue | 19 +- .../frontend/src/pages/my-lists/index.vue | 80 +++- packages/frontend/src/pages/my-lists/list.vue | 24 +- .../frontend/src/pages/settings/general.vue | 229 +++++----- packages/frontend/src/pages/timeline.vue | 6 +- .../frontend/src/pages/user-list-timeline.vue | 1 + 17 files changed, 593 insertions(+), 336 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/users/lists/list-favorite.ts diff --git a/locales/index.d.ts b/locales/index.d.ts index 292559b429..f5be8a3fd4 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -92,6 +92,10 @@ export interface Locale extends ILocale { * キャンセル */ "cancel": string; + /** + * 自分の作成したリスト + */ + "myLists": string; /** * やめておく */ @@ -128,6 +132,14 @@ export interface Locale extends ILocale { * 通知の設定 */ "notificationSettings": string; + /** + * このサーバーの公開のリスト + */ + "localListList": string; + /** + * お気に入りのリスト + */ + "favoriteLists": string; /** * 基本設定 */ @@ -6628,6 +6640,14 @@ export interface Locale extends ILocale { * アイコンデコレーションの最大取付個数 */ "avatarDecorationLimit": string; + /** + * ピン留めリストの最大数 + */ + "listPinnedLimit": string; + /** + * 他鯖のローカルTL除けるやつ(最大値5) + */ + "localTimelineAnyLimit": string; }; "_condition": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index baf07249cd..46f1719271 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -19,6 +19,7 @@ hanntennInfo: "ダークだったらライトのアイコンに、ライトだ ruby: "ルビ" gotIt: "わかった" cancel: "キャンセル" +myLists: "自分の作成したリスト" noThankYou: "やめておく" enterUsername: "ユーザー名を入力" showGlobalTimeline: "グローバルタイムラインを表示する" @@ -28,6 +29,8 @@ noNotifications: "通知はありません" instance: "サーバー" settings: "設定" notificationSettings: "通知の設定" +localListList: "このサーバーの公開のリスト" +favoriteLists: "お気に入りのリスト" basicSettings: "基本設定" otherSettings: "その他の設定" openInWindow: "ウィンドウで開く" @@ -1711,6 +1714,8 @@ _role: canSearchNotes: "ノート検索の利用" canUseTranslator: "翻訳機能の利用" avatarDecorationLimit: "アイコンデコレーションの最大取付個数" + listPinnedLimit: "ピン留めリストの最大数" + localTimelineAnyLimit: "他鯖のローカルTL除けるやつ(最大値5)" _condition: isLocal: "ローカルユーザー" isRemote: "リモートユーザー" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index afa7ebe98b..0e60260a2d 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -60,6 +60,8 @@ export type RolePolicies = { rateLimitFactor: number; avatarDecorationLimit: number; emojiPickerProfileLimit: number; + listPinnedLimit: number; + localTimelineAnyLimit: number; }; export const DEFAULT_POLICIES: RolePolicies = { @@ -91,6 +93,8 @@ export const DEFAULT_POLICIES: RolePolicies = { rateLimitFactor: 1, avatarDecorationLimit: 1, emojiPickerProfileLimit: 2, + listPinnedLimit: 2, + localTimelineAnyLimit: 3, }; @Injectable() @@ -359,6 +363,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)), avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)), emojiPickerProfileLimit: calc('emojiPickerProfileLimit', vs => Math.max(...vs)), + listPinnedLimit: calc('listPinnedLimit', vs => Math.max(...vs)), + localTimelineAnyLimit: calc('localTimelineAnyLimit', vs => Math.max(...vs)), }; } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index c7e3742a8b..a32fed9cc9 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -6,6 +6,7 @@ import { Module } from '@nestjs/common'; import { CoreModule } from '@/core/CoreModule.js'; +import * as ep___users_lists_list_favorite from '@/server/api/endpoints/users/lists/list-favorite.js'; import * as ep___admin_meta from './endpoints/admin/meta.js'; import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js'; @@ -733,6 +734,7 @@ const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', use const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default }; const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default }; const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default }; +const $users_lists_list_favorite: Provider = { provide: 'ep:users/lists/list-favorite', useClass: ep___users_lists_list_favorite.default }; const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass: ep___users_lists_pull.default }; const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default }; const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default }; @@ -1119,6 +1121,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $users_lists_create, $users_lists_delete, $users_lists_list, + $users_lists_list_favorite, $users_lists_pull, $users_lists_push, $users_lists_show, @@ -1496,6 +1499,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $users_lists_create, $users_lists_delete, $users_lists_list, + $users_lists_list_favorite, $users_lists_pull, $users_lists_push, $users_lists_show, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 6d56ea1876..2fcadae307 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -351,6 +351,7 @@ import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js'; import * as ep___users_lists_create from './endpoints/users/lists/create.js'; import * as ep___users_lists_delete from './endpoints/users/lists/delete.js'; import * as ep___users_lists_list from './endpoints/users/lists/list.js'; +import * as ep___users_lists_list_favorite from './endpoints/users/lists/list-favorite.js'; import * as ep___users_lists_pull from './endpoints/users/lists/pull.js'; import * as ep___users_lists_push from './endpoints/users/lists/push.js'; import * as ep___users_lists_show from './endpoints/users/lists/show.js'; @@ -731,6 +732,7 @@ const eps = [ ['users/lists/create', ep___users_lists_create], ['users/lists/delete', ep___users_lists_delete], ['users/lists/list', ep___users_lists_list], + ['users/lists/list-favorite', ep___users_lists_list_favorite], ['users/lists/pull', ep___users_lists_pull], ['users/lists/push', ep___users_lists_push], ['users/lists/show', ep___users_lists_show], diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 71c2b8054e..618df922a4 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -92,7 +92,7 @@ export default class extends Endpoint { // eslint- const list = await this.userListsRepository.findOneBy({ id: ps.listId, - userId: me.id, + isPublic: true, }); if (list == null) { diff --git a/packages/backend/src/server/api/endpoints/users/lists/list-favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/list-favorite.ts new file mode 100644 index 0000000000..a7d01afc86 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/lists/list-favorite.ts @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ApiError } from '@/server/api/error.js'; +import { DI } from '@/di-symbols.js'; +import type { UserListsRepository, UserListFavoritesRepository } from '@/models/_.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { QueryService } from '@/core/QueryService.js'; +export const meta = { + tags: ['lists', 'account'], + + requireCredential: false, + + kind: 'read:account', + + description: 'Show all lists that the authenticated user has created.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'UserList', + }, + }, + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: 'a8af4a82-0980-4cc4-a6af-8b0ffd54465e', + }, + remoteUser: { + message: 'Not allowed to load the remote user\'s list', + code: 'REMOTE_USER_NOT_ALLOWED', + id: '53858f1b-3315-4a01-81b7-db9b48d4b79a', + }, + invalidParam: { + message: 'Invalid param.', + code: 'INVALID_PARAM', + id: 'ab36de0e-29e9-48cb-9732-d82f1281620d', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + }, + required: [], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint { + constructor( + @Inject(DI.userListFavoritesRepository) + private userListFavoritesRepository: UserListFavoritesRepository, + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + private userListEntityService: UserListEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + if (!me) { + throw new ApiError(meta.errors.noSuchUser); + } + const favorites = await this.userListFavoritesRepository.findBy({ userId: me.id }); + + if (favorites == null) { + return []; + } + const listIds = favorites.map(favorite => favorite.userListId); + const lists = await this.userListsRepository.findBy({ id: In(listIds) }); + return await Promise.all(lists.map(async list => await this.userListEntityService.pack(list))); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 0e86dd3a68..6b7d05a712 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -51,6 +51,7 @@ export const paramDef = { type: 'object', properties: { userId: { type: 'string', format: 'misskey:id' }, + publicAll: { type: 'boolean', nullable: false }, }, required: [], } as const; @@ -67,22 +68,29 @@ export default class extends Endpoint { private userListEntityService: UserListEntityService, ) { super(meta, paramDef, async (ps, me) => { - if (typeof ps.userId !== 'undefined') { - const user = await this.usersRepository.findOneBy({ id: ps.userId }); - if (user === null) throw new ApiError(meta.errors.noSuchUser); - if (user.host !== null) throw new ApiError(meta.errors.remoteUser); - } else if (me === null) { - throw new ApiError(meta.errors.invalidParam); + if (!ps.publicAll ) { + if (typeof ps.userId !== 'undefined') { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + if (user === null) throw new ApiError(meta.errors.noSuchUser); + if (user.host !== null) throw new ApiError(meta.errors.remoteUser); + } else if (me === null) { + throw new ApiError(meta.errors.invalidParam); + } + + const userLists = await this.userListsRepository.findBy(typeof ps.userId === 'undefined' && me !== null ? { + userId: me.id, + } : { + userId: ps.userId, + isPublic: true, + }); + + return await Promise.all(userLists.map(x => this.userListEntityService.pack(x))); + } else { + const userLists = await this.userListsRepository.findBy({ + isPublic: true, + }); + return await Promise.all(userLists.map(x => this.userListEntityService.pack(x))); } - - const userLists = await this.userListsRepository.findBy(typeof ps.userId === 'undefined' && me !== null ? { - userId: me.id, - } : { - userId: ps.userId, - isPublic: true, - }); - - return await Promise.all(userLists.map(x => this.userListEntityService.pack(x))); }); } } diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts index 20950add80..17d813a3f2 100644 --- a/packages/frontend/src/cache.ts +++ b/packages/frontend/src/cache.ts @@ -11,3 +11,4 @@ export const clipsCache = new Cache(1000 * 60 * 30, () export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list')); export const userListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list')); export const antennasCache = new Cache(1000 * 60 * 30, () => misskeyApi('antennas/list')); +export const userFavoriteListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list-favorite')); diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index f07a97933d..c98aef3189 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -99,7 +99,9 @@ export const ROLE_POLICIES = [ 'userEachUserListsLimit', 'rateLimitFactor', 'avatarDecorationLimit', - 'emojiPickerProfileLimit' + 'emojiPickerProfileLimit', + 'listPinnedLimit', + 'localTimelineAnyLimit', ] as const; // なんか動かない @@ -129,7 +131,7 @@ export const MFM_PARAMS: Record = { position: ['x=', 'y='], fg: ['color='], bg: ['color='], - border: ['width=', 'style=', 'color=', 'radius=', 'noclip'], + border: ['width=', 'style=', 'color=', 'radius=', 'noclip'], font: ['serif', 'monospace', 'cursive', 'fantasy', 'emoji', 'math'], blur: [], rainbow: ['speed=', 'delay='], diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index dc0d43f7b7..a3c415f2e1 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -23,212 +23,255 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n.ts.save }} diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue index 03db02a350..59e39e4bae 100644 --- a/packages/frontend/src/pages/list.vue +++ b/packages/frontend/src/pages/list.vue @@ -6,7 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -44,6 +46,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkButton from '@/components/MkButton.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { serverErrorImageUrl } from '@/instance.js'; +import MkFolder from '@/components/MkFolder.vue'; const props = defineProps<{ listId: string; diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue index 295112b0ba..21ce2f960d 100644 --- a/packages/frontend/src/pages/my-lists/index.vue +++ b/packages/frontend/src/pages/my-lists/index.vue @@ -7,44 +7,87 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
-
- -
{{ i18n.ts.nothing }}
+ + + +
+
+
+ +
{{ i18n.ts.nothing }}
+
+
+ +
+ +
{{ list.name }} ({{ i18n.tsx.nUsers({ n: `${list.userIds.length}` }) }})
+ +
+
+ + +
+
+
+ +
{{ i18n.ts.nothing }}
+
+
- {{ i18n.ts.createList }} - -
- -
{{ list.name }} ({{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }})
- -
+
+ +
{{ list.name }} ({{ i18n.tsx.nUsers({ n: `${list.userIds.length}` }) }})
+ +
+
-
+
+ + +
+
+
+ +
{{ i18n.ts.nothing }}
+
+
+ +
+ +
{{ list.name }} ({{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }})
+ +
+
+
+