summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-02-05 10:37:03 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-02-05 10:37:03 +0900
commit6a3039f7b7d635a3a3e00ea9501bc44b1c9dc76c (patch)
tree77215228ac223088049db5820e94ca092ee87ffa /packages
parentupdate CHANGELOG.md (diff)
downloadsharkey-6a3039f7b7d635a3a3e00ea9501bc44b1c9dc76c.tar.gz
sharkey-6a3039f7b7d635a3a3e00ea9501bc44b1c9dc76c.tar.bz2
sharkey-6a3039f7b7d635a3a3e00ea9501bc44b1c9dc76c.zip
feat: ロールにアイコンを設定してユーザー名の横に表示できるように
Resolve #9761
Diffstat (limited to 'packages')
-rw-r--r--packages/backend/migration/1675557528704-role-icon-badge.js13
-rw-r--r--packages/backend/src/core/RoleService.ts13
-rw-r--r--packages/backend/src/core/entities/RoleEntityService.ts2
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts6
-rw-r--r--packages/backend/src/models/entities/Role.ts11
-rw-r--r--packages/backend/src/server/api/endpoints/admin/roles/create.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/roles/update.ts6
-rw-r--r--packages/frontend/src/components/MkNoteHeader.vue16
-rw-r--r--packages/frontend/src/pages/admin/roles.editor.vue35
-rw-r--r--packages/frontend/src/pages/user/home.vue5
10 files changed, 102 insertions, 11 deletions
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<RolePolicies> {
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<typeof meta, typeof paramDef> {
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<typeof meta, typeof paramDef> {
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 @@
</MkA>
<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
<div :class="$style.username"><MkAcct :user="note.user"/></div>
+ <div v-if="note.user.badgeRoles" :class="$style.badgeRoles">
+ <img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
+ </div>
<div :class="$style.info">
<MkA :to="notePage(note)">
<MkTime :time="note.createdAt"/>
@@ -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;
+ }
+}
</style>
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 @@
<template #caption>#RRGGBB</template>
</MkInput>
+ <MkInput v-model="iconUrl">
+ <template #label>{{ i18n.ts._role.iconUrl }}</template>
+ </MkInput>
+
<MkSelect v-model="rolePermission" :readonly="readonly">
<template #label><i class="ti ti-shield-lock"></i> {{ i18n.ts._role.permission }}</template>
<template #caption><div v-html="i18n.ts._role.descriptionOfPermission.replaceAll('\n', '<br>')"></div></template>
@@ -35,6 +39,21 @@
</div>
</MkFolder>
+ <MkSwitch v-model="canEditMembersByModerator" :readonly="readonly">
+ <template #label>{{ i18n.ts._role.canEditMembersByModerator }}</template>
+ <template #caption>{{ i18n.ts._role.descriptionOfCanEditMembersByModerator }}</template>
+ </MkSwitch>
+
+ <MkSwitch v-model="isPublic" :readonly="readonly">
+ <template #label>{{ i18n.ts._role.isPublic }}</template>
+ <template #caption>{{ i18n.ts._role.descriptionOfIsPublic }}</template>
+ </MkSwitch>
+
+ <MkSwitch v-model="asBadge" :readonly="readonly">
+ <template #label>{{ i18n.ts._role.asBadge }}</template>
+ <template #caption>{{ i18n.ts._role.descriptionOfAsBadge }}</template>
+ </MkSwitch>
+
<FormSlot>
<template #label><i class="ti ti-license"></i> {{ i18n.ts._role.policies }}</template>
<div class="_gaps_s">
@@ -358,16 +377,6 @@
</div>
</FormSlot>
- <MkSwitch v-model="canEditMembersByModerator" :readonly="readonly">
- <template #label>{{ i18n.ts._role.canEditMembersByModerator }}</template>
- <template #caption>{{ i18n.ts._role.descriptionOfCanEditMembersByModerator }}</template>
- </MkSwitch>
-
- <MkSwitch v-model="isPublic" :readonly="readonly">
- <template #label>{{ i18n.ts._role.isPublic }}</template>
- <template #caption>{{ i18n.ts._role.descriptionOfIsPublic }}</template>
- </MkSwitch>
-
<div v-if="!readonly" class="_buttons">
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ role ? i18n.ts.save : i18n.ts.create }}</MkButton>
</div>
@@ -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<Record<typeof ROLE_POLICIES[number], { useDefault: boolean; priority: number; value: any; }>>({});
@@ -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 @@
</div>
</div>
<div v-if="user.roles.length > 0" class="roles">
- <span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">{{ role.name }}</span>
+ <span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
+ <img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
+ {{ role.name }}
+ </span>
</div>
<div class="description">
<MkOmit>