From 6a3039f7b7d635a3a3e00ea9501bc44b1c9dc76c Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 5 Feb 2023 10:37:03 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=83=AD=E3=83=BC=E3=83=AB=E3=81=AB?= =?UTF-8?q?=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3=E3=82=92=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E5=90=8D?= =?UTF-8?q?=E3=81=AE=E6=A8=AA=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve #9761 --- CHANGELOG.md | 1 + locales/ja-JP.yml | 5 ++- .../1675557528704-role-icon-badge.js | 13 +++++++ packages/backend/src/core/RoleService.ts | 13 +++++++ .../src/core/entities/RoleEntityService.ts | 2 ++ .../src/core/entities/UserEntityService.ts | 6 ++++ packages/backend/src/models/entities/Role.ts | 11 ++++++ .../api/endpoints/admin/roles/create.ts | 6 ++++ .../api/endpoints/admin/roles/update.ts | 6 ++++ .../frontend/src/components/MkNoteHeader.vue | 16 +++++++++ .../frontend/src/pages/admin/roles.editor.vue | 35 +++++++++++++------ packages/frontend/src/pages/user/home.vue | 5 ++- 12 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 packages/backend/migration/1675557528704-role-icon-badge.js diff --git a/CHANGELOG.md b/CHANGELOG.md index a746f8a2ae..d283d98555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ You should also include the user name that made the change. ## 13.x.x (unreleased) ### Improvements +- ロールにアイコンを設定してユーザー名の横に表示できるように ### Bugfixes - diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a241d54b47..4aaf63b628 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1184,7 +1184,7 @@ _role: description: "ロールの説明" permission: "ロールの権限" descriptionOfPermission: "モデレーターは基本的なモデレーションに関する操作を行えます。\n管理者はインスタンスの全ての設定を変更できます。" - assignTarget: "アサインターゲット" + assignTarget: "アサイン" descriptionOfAssignTarget: "マニュアルは誰がこのロールに含まれるかを手動で管理します。\nコンディショナルは条件を設定し、それに合致するユーザーが自動で含まれるようになります。" manual: "マニュアル" conditional: "コンディショナル" @@ -1197,6 +1197,9 @@ _role: baseRole: "ベースロール" useBaseValue: "ベースロールの値を使用" chooseRoleToAssign: "アサインするロールを選択" + iconUrl: "アイコン画像のURL" + asBadge: "バッジとして表示" + descriptionOfAsBadge: "オンにすると、ユーザー名の横にロールのアイコンが表示されます。" canEditMembersByModerator: "モデレーターのメンバー編集を許可" descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。" priority: "優先度" diff --git a/packages/backend/migration/1675557528704-role-icon-badge.js b/packages/backend/migration/1675557528704-role-icon-badge.js new file mode 100644 index 0000000000..0ebca088e3 --- /dev/null +++ b/packages/backend/migration/1675557528704-role-icon-badge.js @@ -0,0 +1,13 @@ +export class roleIconBadge1675557528704 { + name = 'roleIconBadge1675557528704' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "role" ADD "iconUrl" character varying(512)`); + await queryRunner.query(`ALTER TABLE "role" ADD "asBadge" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "asBadge"`); + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "iconUrl"`); + } +} diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index f8f9231cdd..d15d8c0aee 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -202,6 +202,19 @@ export class RoleService implements OnApplicationShutdown { return [...assignedRoles, ...matchedCondRoles]; } + /** + * 指定ユーザーのバッジロール一覧取得 + */ + @bindThis + public async getUserBadgeRoles(userId: User['id']) { + const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); + const assignedRoleIds = assigns.map(x => x.roleId); + const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); + const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id)); + // コンディショナルロールも含めるのは負荷高そうだから一旦無し + return assignedBadgeRoles; + } + @bindThis public async getUserPolicies(userId: User['id'] | null): Promise { const meta = await this.metaService.fetch(); diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index 52f3374468..dbb89ff19b 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -56,11 +56,13 @@ export class RoleEntityService { name: role.name, description: role.description, color: role.color, + iconUrl: role.iconUrl, target: role.target, condFormula: role.condFormula, isPublic: role.isPublic, isAdministrator: role.isAdministrator, isModerator: role.isModerator, + asBadge: role.asBadge, canEditMembersByModerator: role.canEditMembersByModerator, policies: policies, usersCount: assigns.length, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index ff42c07359..eea9d5567d 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -415,6 +415,11 @@ export class UserEntityService implements OnModuleInit { } : undefined) : undefined, emojis: this.customEmojiService.populateEmojis(user.emojis, user.host), onlineStatus: this.getOnlineStatus(user), + // パフォーマンス上の理由でローカルユーザーのみ + badgeRoles: user.host == null ? this.roleService.getUserBadgeRoles(user.id).then(rs => rs.map(r => ({ + name: r.name, + iconUrl: r.iconUrl, + }))) : undefined, ...(opts.detail ? { url: profile!.url, @@ -454,6 +459,7 @@ export class UserEntityService implements OnModuleInit { id: role.id, name: role.name, color: role.color, + iconUrl: role.iconUrl, description: role.description, isModerator: role.isModerator, isAdministrator: role.isAdministrator, diff --git a/packages/backend/src/models/entities/Role.ts b/packages/backend/src/models/entities/Role.ts index abd5f864a2..8cf6811863 100644 --- a/packages/backend/src/models/entities/Role.ts +++ b/packages/backend/src/models/entities/Role.ts @@ -102,6 +102,11 @@ export class Role { }) public color: string | null; + @Column('varchar', { + length: 512, nullable: true, + }) + public iconUrl: string | null; + @Column('enum', { enum: ['manual', 'conditional'], default: 'manual', @@ -118,6 +123,12 @@ export class Role { }) public isPublic: boolean; + // trueの場合ユーザー名の横にバッジとして表示 + @Column('boolean', { + default: false, + }) + public asBadge: boolean; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts index f136c6d624..1a2a9fb747 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts @@ -19,11 +19,13 @@ export const paramDef = { name: { type: 'string' }, description: { type: 'string' }, color: { type: 'string', nullable: true }, + iconUrl: { type: 'string', nullable: true }, target: { type: 'string' }, condFormula: { type: 'object' }, isPublic: { type: 'boolean' }, isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, + asBadge: { type: 'boolean' }, canEditMembersByModerator: { type: 'boolean' }, policies: { type: 'object', @@ -33,11 +35,13 @@ export const paramDef = { 'name', 'description', 'color', + 'iconUrl', 'target', 'condFormula', 'isPublic', 'isModerator', 'isAdministrator', + 'asBadge', 'canEditMembersByModerator', 'policies', ], @@ -64,11 +68,13 @@ export default class extends Endpoint { name: ps.name, description: ps.description, color: ps.color, + iconUrl: ps.iconUrl, target: ps.target, condFormula: ps.condFormula, isPublic: ps.isPublic, isAdministrator: ps.isAdministrator, isModerator: ps.isModerator, + asBadge: ps.asBadge, canEditMembersByModerator: ps.canEditMembersByModerator, policies: ps.policies, }).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0])); 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 fc4c3d8f11..c9f4a9fed8 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -27,11 +27,13 @@ export const paramDef = { name: { type: 'string' }, description: { type: 'string' }, color: { type: 'string', nullable: true }, + iconUrl: { type: 'string', nullable: true }, target: { type: 'string' }, condFormula: { type: 'object' }, isPublic: { type: 'boolean' }, isModerator: { type: 'boolean' }, isAdministrator: { type: 'boolean' }, + asBadge: { type: 'boolean' }, canEditMembersByModerator: { type: 'boolean' }, policies: { type: 'object', @@ -42,11 +44,13 @@ export const paramDef = { 'name', 'description', 'color', + 'iconUrl', 'target', 'condFormula', 'isPublic', 'isModerator', 'isAdministrator', + 'asBadge', 'canEditMembersByModerator', 'policies', ], @@ -73,11 +77,13 @@ export default class extends Endpoint { name: ps.name, description: ps.description, color: ps.color, + iconUrl: ps.iconUrl, target: ps.target, condFormula: ps.condFormula, isPublic: ps.isPublic, isModerator: ps.isModerator, isAdministrator: ps.isAdministrator, + asBadge: ps.asBadge, canEditMembersByModerator: ps.canEditMembersByModerator, policies: ps.policies, }); diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index 8771168a42..6b43f14665 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -5,6 +5,9 @@
bot
+
+ +
@@ -77,4 +80,17 @@ defineProps<{ margin-left: auto; font-size: 0.9em; } + +.badgeRoles { + margin: 0 .5em 0 0; +} + +.badgeRole { + height: 1.3em; + vertical-align: -20%; + + & + .badgeRole { + margin-left: .125em; + } +} diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index ae5ef39bae..086537a94a 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -13,6 +13,10 @@ + + + + @@ -35,6 +39,21 @@
+ + + + + + + + + + + + + + +
@@ -358,16 +377,6 @@
- - - - - - - - - -
{{ role ? i18n.ts.save : i18n.ts.create }}
@@ -426,9 +435,11 @@ let name = $ref(role?.name ?? 'New Role'); let description = $ref(role?.description ?? ''); let rolePermission = $ref(role?.isAdministrator ? 'administrator' : role?.isModerator ? 'moderator' : 'normal'); let color = $ref(role?.color ?? null); +let iconUrl = $ref(role?.iconUrl ?? null); let target = $ref(role?.target ?? 'manual'); let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' }); let isPublic = $ref(role?.isPublic ?? false); +let asBadge = $ref(role?.asBadge ?? false); let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false); const policies = reactive>({}); @@ -466,11 +477,13 @@ async function save() { name, description, color: color === '' ? null : color, + iconUrl: iconUrl === '' ? null : iconUrl, target, condFormula, isAdministrator: rolePermission === 'administrator', isModerator: rolePermission === 'moderator', isPublic, + asBadge, canEditMembersByModerator, policies, }); @@ -480,11 +493,13 @@ async function save() { name, description, color: color === '' ? null : color, + iconUrl: iconUrl === '' ? null : iconUrl, target, condFormula, isAdministrator: rolePermission === 'administrator', isModerator: rolePermission === 'moderator', isPublic, + asBadge, canEditMembersByModerator, policies, }); diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index c960b31274..56858a9377 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -39,7 +39,10 @@
- {{ role.name }} + + + {{ role.name }} +