parent
868c8fffb3
commit
6a3039f7b7
|
@ -11,6 +11,7 @@ You should also include the user name that made the change.
|
||||||
## 13.x.x (unreleased)
|
## 13.x.x (unreleased)
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
- ロールにアイコンを設定してユーザー名の横に表示できるように
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
-
|
-
|
||||||
|
|
|
@ -1184,7 +1184,7 @@ _role:
|
||||||
description: "ロールの説明"
|
description: "ロールの説明"
|
||||||
permission: "ロールの権限"
|
permission: "ロールの権限"
|
||||||
descriptionOfPermission: "<b>モデレーター</b>は基本的なモデレーションに関する操作を行えます。\n<b>管理者</b>はインスタンスの全ての設定を変更できます。"
|
descriptionOfPermission: "<b>モデレーター</b>は基本的なモデレーションに関する操作を行えます。\n<b>管理者</b>はインスタンスの全ての設定を変更できます。"
|
||||||
assignTarget: "アサインターゲット"
|
assignTarget: "アサイン"
|
||||||
descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれるかを手動で管理します。\n<b>コンディショナル</b>は条件を設定し、それに合致するユーザーが自動で含まれるようになります。"
|
descriptionOfAssignTarget: "<b>マニュアル</b>は誰がこのロールに含まれるかを手動で管理します。\n<b>コンディショナル</b>は条件を設定し、それに合致するユーザーが自動で含まれるようになります。"
|
||||||
manual: "マニュアル"
|
manual: "マニュアル"
|
||||||
conditional: "コンディショナル"
|
conditional: "コンディショナル"
|
||||||
|
@ -1197,6 +1197,9 @@ _role:
|
||||||
baseRole: "ベースロール"
|
baseRole: "ベースロール"
|
||||||
useBaseValue: "ベースロールの値を使用"
|
useBaseValue: "ベースロールの値を使用"
|
||||||
chooseRoleToAssign: "アサインするロールを選択"
|
chooseRoleToAssign: "アサインするロールを選択"
|
||||||
|
iconUrl: "アイコン画像のURL"
|
||||||
|
asBadge: "バッジとして表示"
|
||||||
|
descriptionOfAsBadge: "オンにすると、ユーザー名の横にロールのアイコンが表示されます。"
|
||||||
canEditMembersByModerator: "モデレーターのメンバー編集を許可"
|
canEditMembersByModerator: "モデレーターのメンバー編集を許可"
|
||||||
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。"
|
descriptionOfCanEditMembersByModerator: "オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。"
|
||||||
priority: "優先度"
|
priority: "優先度"
|
||||||
|
|
|
@ -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"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -202,6 +202,19 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
return [...assignedRoles, ...matchedCondRoles];
|
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
|
@bindThis
|
||||||
public async getUserPolicies(userId: User['id'] | null): Promise<RolePolicies> {
|
public async getUserPolicies(userId: User['id'] | null): Promise<RolePolicies> {
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
|
|
|
@ -56,11 +56,13 @@ export class RoleEntityService {
|
||||||
name: role.name,
|
name: role.name,
|
||||||
description: role.description,
|
description: role.description,
|
||||||
color: role.color,
|
color: role.color,
|
||||||
|
iconUrl: role.iconUrl,
|
||||||
target: role.target,
|
target: role.target,
|
||||||
condFormula: role.condFormula,
|
condFormula: role.condFormula,
|
||||||
isPublic: role.isPublic,
|
isPublic: role.isPublic,
|
||||||
isAdministrator: role.isAdministrator,
|
isAdministrator: role.isAdministrator,
|
||||||
isModerator: role.isModerator,
|
isModerator: role.isModerator,
|
||||||
|
asBadge: role.asBadge,
|
||||||
canEditMembersByModerator: role.canEditMembersByModerator,
|
canEditMembersByModerator: role.canEditMembersByModerator,
|
||||||
policies: policies,
|
policies: policies,
|
||||||
usersCount: assigns.length,
|
usersCount: assigns.length,
|
||||||
|
|
|
@ -415,6 +415,11 @@ export class UserEntityService implements OnModuleInit {
|
||||||
} : undefined) : undefined,
|
} : undefined) : undefined,
|
||||||
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
||||||
onlineStatus: this.getOnlineStatus(user),
|
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 ? {
|
...(opts.detail ? {
|
||||||
url: profile!.url,
|
url: profile!.url,
|
||||||
|
@ -454,6 +459,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
id: role.id,
|
id: role.id,
|
||||||
name: role.name,
|
name: role.name,
|
||||||
color: role.color,
|
color: role.color,
|
||||||
|
iconUrl: role.iconUrl,
|
||||||
description: role.description,
|
description: role.description,
|
||||||
isModerator: role.isModerator,
|
isModerator: role.isModerator,
|
||||||
isAdministrator: role.isAdministrator,
|
isAdministrator: role.isAdministrator,
|
||||||
|
|
|
@ -102,6 +102,11 @@ export class Role {
|
||||||
})
|
})
|
||||||
public color: string | null;
|
public color: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512, nullable: true,
|
||||||
|
})
|
||||||
|
public iconUrl: string | null;
|
||||||
|
|
||||||
@Column('enum', {
|
@Column('enum', {
|
||||||
enum: ['manual', 'conditional'],
|
enum: ['manual', 'conditional'],
|
||||||
default: 'manual',
|
default: 'manual',
|
||||||
|
@ -118,6 +123,12 @@ export class Role {
|
||||||
})
|
})
|
||||||
public isPublic: boolean;
|
public isPublic: boolean;
|
||||||
|
|
||||||
|
// trueの場合ユーザー名の横にバッジとして表示
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public asBadge: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,11 +19,13 @@ export const paramDef = {
|
||||||
name: { type: 'string' },
|
name: { type: 'string' },
|
||||||
description: { type: 'string' },
|
description: { type: 'string' },
|
||||||
color: { type: 'string', nullable: true },
|
color: { type: 'string', nullable: true },
|
||||||
|
iconUrl: { type: 'string', nullable: true },
|
||||||
target: { type: 'string' },
|
target: { type: 'string' },
|
||||||
condFormula: { type: 'object' },
|
condFormula: { type: 'object' },
|
||||||
isPublic: { type: 'boolean' },
|
isPublic: { type: 'boolean' },
|
||||||
isModerator: { type: 'boolean' },
|
isModerator: { type: 'boolean' },
|
||||||
isAdministrator: { type: 'boolean' },
|
isAdministrator: { type: 'boolean' },
|
||||||
|
asBadge: { type: 'boolean' },
|
||||||
canEditMembersByModerator: { type: 'boolean' },
|
canEditMembersByModerator: { type: 'boolean' },
|
||||||
policies: {
|
policies: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -33,11 +35,13 @@ export const paramDef = {
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'color',
|
'color',
|
||||||
|
'iconUrl',
|
||||||
'target',
|
'target',
|
||||||
'condFormula',
|
'condFormula',
|
||||||
'isPublic',
|
'isPublic',
|
||||||
'isModerator',
|
'isModerator',
|
||||||
'isAdministrator',
|
'isAdministrator',
|
||||||
|
'asBadge',
|
||||||
'canEditMembersByModerator',
|
'canEditMembersByModerator',
|
||||||
'policies',
|
'policies',
|
||||||
],
|
],
|
||||||
|
@ -64,11 +68,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
name: ps.name,
|
name: ps.name,
|
||||||
description: ps.description,
|
description: ps.description,
|
||||||
color: ps.color,
|
color: ps.color,
|
||||||
|
iconUrl: ps.iconUrl,
|
||||||
target: ps.target,
|
target: ps.target,
|
||||||
condFormula: ps.condFormula,
|
condFormula: ps.condFormula,
|
||||||
isPublic: ps.isPublic,
|
isPublic: ps.isPublic,
|
||||||
isAdministrator: ps.isAdministrator,
|
isAdministrator: ps.isAdministrator,
|
||||||
isModerator: ps.isModerator,
|
isModerator: ps.isModerator,
|
||||||
|
asBadge: ps.asBadge,
|
||||||
canEditMembersByModerator: ps.canEditMembersByModerator,
|
canEditMembersByModerator: ps.canEditMembersByModerator,
|
||||||
policies: ps.policies,
|
policies: ps.policies,
|
||||||
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
|
@ -27,11 +27,13 @@ export const paramDef = {
|
||||||
name: { type: 'string' },
|
name: { type: 'string' },
|
||||||
description: { type: 'string' },
|
description: { type: 'string' },
|
||||||
color: { type: 'string', nullable: true },
|
color: { type: 'string', nullable: true },
|
||||||
|
iconUrl: { type: 'string', nullable: true },
|
||||||
target: { type: 'string' },
|
target: { type: 'string' },
|
||||||
condFormula: { type: 'object' },
|
condFormula: { type: 'object' },
|
||||||
isPublic: { type: 'boolean' },
|
isPublic: { type: 'boolean' },
|
||||||
isModerator: { type: 'boolean' },
|
isModerator: { type: 'boolean' },
|
||||||
isAdministrator: { type: 'boolean' },
|
isAdministrator: { type: 'boolean' },
|
||||||
|
asBadge: { type: 'boolean' },
|
||||||
canEditMembersByModerator: { type: 'boolean' },
|
canEditMembersByModerator: { type: 'boolean' },
|
||||||
policies: {
|
policies: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -42,11 +44,13 @@ export const paramDef = {
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'color',
|
'color',
|
||||||
|
'iconUrl',
|
||||||
'target',
|
'target',
|
||||||
'condFormula',
|
'condFormula',
|
||||||
'isPublic',
|
'isPublic',
|
||||||
'isModerator',
|
'isModerator',
|
||||||
'isAdministrator',
|
'isAdministrator',
|
||||||
|
'asBadge',
|
||||||
'canEditMembersByModerator',
|
'canEditMembersByModerator',
|
||||||
'policies',
|
'policies',
|
||||||
],
|
],
|
||||||
|
@ -73,11 +77,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
name: ps.name,
|
name: ps.name,
|
||||||
description: ps.description,
|
description: ps.description,
|
||||||
color: ps.color,
|
color: ps.color,
|
||||||
|
iconUrl: ps.iconUrl,
|
||||||
target: ps.target,
|
target: ps.target,
|
||||||
condFormula: ps.condFormula,
|
condFormula: ps.condFormula,
|
||||||
isPublic: ps.isPublic,
|
isPublic: ps.isPublic,
|
||||||
isModerator: ps.isModerator,
|
isModerator: ps.isModerator,
|
||||||
isAdministrator: ps.isAdministrator,
|
isAdministrator: ps.isAdministrator,
|
||||||
|
asBadge: ps.asBadge,
|
||||||
canEditMembersByModerator: ps.canEditMembersByModerator,
|
canEditMembersByModerator: ps.canEditMembersByModerator,
|
||||||
policies: ps.policies,
|
policies: ps.policies,
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
</MkA>
|
</MkA>
|
||||||
<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
|
<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
|
||||||
<div :class="$style.username"><MkAcct :user="note.user"/></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">
|
<div :class="$style.info">
|
||||||
<MkA :to="notePage(note)">
|
<MkA :to="notePage(note)">
|
||||||
<MkTime :time="note.createdAt"/>
|
<MkTime :time="note.createdAt"/>
|
||||||
|
@ -77,4 +80,17 @@ defineProps<{
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badgeRoles {
|
||||||
|
margin: 0 .5em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badgeRole {
|
||||||
|
height: 1.3em;
|
||||||
|
vertical-align: -20%;
|
||||||
|
|
||||||
|
& + .badgeRole {
|
||||||
|
margin-left: .125em;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
<template #caption>#RRGGBB</template>
|
<template #caption>#RRGGBB</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="iconUrl">
|
||||||
|
<template #label>{{ i18n.ts._role.iconUrl }}</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
<MkSelect v-model="rolePermission" :readonly="readonly">
|
<MkSelect v-model="rolePermission" :readonly="readonly">
|
||||||
<template #label><i class="ti ti-shield-lock"></i> {{ i18n.ts._role.permission }}</template>
|
<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>
|
<template #caption><div v-html="i18n.ts._role.descriptionOfPermission.replaceAll('\n', '<br>')"></div></template>
|
||||||
|
@ -35,6 +39,21 @@
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</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>
|
<FormSlot>
|
||||||
<template #label><i class="ti ti-license"></i> {{ i18n.ts._role.policies }}</template>
|
<template #label><i class="ti ti-license"></i> {{ i18n.ts._role.policies }}</template>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
|
@ -358,16 +377,6 @@
|
||||||
</div>
|
</div>
|
||||||
</FormSlot>
|
</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">
|
<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>
|
<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ role ? i18n.ts.save : i18n.ts.create }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -426,9 +435,11 @@ let name = $ref(role?.name ?? 'New Role');
|
||||||
let description = $ref(role?.description ?? '');
|
let description = $ref(role?.description ?? '');
|
||||||
let rolePermission = $ref(role?.isAdministrator ? 'administrator' : role?.isModerator ? 'moderator' : 'normal');
|
let rolePermission = $ref(role?.isAdministrator ? 'administrator' : role?.isModerator ? 'moderator' : 'normal');
|
||||||
let color = $ref(role?.color ?? null);
|
let color = $ref(role?.color ?? null);
|
||||||
|
let iconUrl = $ref(role?.iconUrl ?? null);
|
||||||
let target = $ref(role?.target ?? 'manual');
|
let target = $ref(role?.target ?? 'manual');
|
||||||
let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' });
|
let condFormula = $ref(role?.condFormula ?? { id: uuid(), type: 'isRemote' });
|
||||||
let isPublic = $ref(role?.isPublic ?? false);
|
let isPublic = $ref(role?.isPublic ?? false);
|
||||||
|
let asBadge = $ref(role?.asBadge ?? false);
|
||||||
let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false);
|
let canEditMembersByModerator = $ref(role?.canEditMembersByModerator ?? false);
|
||||||
|
|
||||||
const policies = reactive<Record<typeof ROLE_POLICIES[number], { useDefault: boolean; priority: number; value: any; }>>({});
|
const policies = reactive<Record<typeof ROLE_POLICIES[number], { useDefault: boolean; priority: number; value: any; }>>({});
|
||||||
|
@ -466,11 +477,13 @@ async function save() {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
color: color === '' ? null : color,
|
color: color === '' ? null : color,
|
||||||
|
iconUrl: iconUrl === '' ? null : iconUrl,
|
||||||
target,
|
target,
|
||||||
condFormula,
|
condFormula,
|
||||||
isAdministrator: rolePermission === 'administrator',
|
isAdministrator: rolePermission === 'administrator',
|
||||||
isModerator: rolePermission === 'moderator',
|
isModerator: rolePermission === 'moderator',
|
||||||
isPublic,
|
isPublic,
|
||||||
|
asBadge,
|
||||||
canEditMembersByModerator,
|
canEditMembersByModerator,
|
||||||
policies,
|
policies,
|
||||||
});
|
});
|
||||||
|
@ -480,11 +493,13 @@ async function save() {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
color: color === '' ? null : color,
|
color: color === '' ? null : color,
|
||||||
|
iconUrl: iconUrl === '' ? null : iconUrl,
|
||||||
target,
|
target,
|
||||||
condFormula,
|
condFormula,
|
||||||
isAdministrator: rolePermission === 'administrator',
|
isAdministrator: rolePermission === 'administrator',
|
||||||
isModerator: rolePermission === 'moderator',
|
isModerator: rolePermission === 'moderator',
|
||||||
isPublic,
|
isPublic,
|
||||||
|
asBadge,
|
||||||
canEditMembersByModerator,
|
canEditMembersByModerator,
|
||||||
policies,
|
policies,
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,7 +39,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.roles.length > 0" class="roles">
|
<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>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<MkOmit>
|
<MkOmit>
|
||||||
|
|
Loading…
Reference in New Issue