feat: アイコンデコレーションの管理をロールで設定できるように (#12173)
* アイコンデコレーションの管理をロールで設定できるように * インポートミス * Update packages/frontend/src/ui/_common_/common.ts Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * Update packages/frontend/src/ui/_common_/common.ts Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
parent
52dbab56a4
commit
4f180ad45c
14 changed files with 52 additions and 9 deletions
2
locales/index.d.ts
vendored
2
locales/index.d.ts
vendored
|
@ -982,6 +982,7 @@ export interface Locale {
|
||||||
"unassign": string;
|
"unassign": string;
|
||||||
"color": string;
|
"color": string;
|
||||||
"manageCustomEmojis": string;
|
"manageCustomEmojis": string;
|
||||||
|
"manageAvatarDecorations": string;
|
||||||
"youCannotCreateAnymore": string;
|
"youCannotCreateAnymore": string;
|
||||||
"cannotPerformTemporary": string;
|
"cannotPerformTemporary": string;
|
||||||
"cannotPerformTemporaryDescription": string;
|
"cannotPerformTemporaryDescription": string;
|
||||||
|
@ -1575,6 +1576,7 @@ export interface Locale {
|
||||||
"inviteLimitCycle": string;
|
"inviteLimitCycle": string;
|
||||||
"inviteExpirationTime": string;
|
"inviteExpirationTime": string;
|
||||||
"canManageCustomEmojis": string;
|
"canManageCustomEmojis": string;
|
||||||
|
"canManageAvatarDecorations": string;
|
||||||
"driveCapacity": string;
|
"driveCapacity": string;
|
||||||
"alwaysMarkNsfw": string;
|
"alwaysMarkNsfw": string;
|
||||||
"pinMax": string;
|
"pinMax": string;
|
||||||
|
|
|
@ -979,6 +979,7 @@ assign: "アサイン"
|
||||||
unassign: "アサインを解除"
|
unassign: "アサインを解除"
|
||||||
color: "色"
|
color: "色"
|
||||||
manageCustomEmojis: "カスタム絵文字の管理"
|
manageCustomEmojis: "カスタム絵文字の管理"
|
||||||
|
manageAvatarDecorations: "アバターデコレーションの管理"
|
||||||
youCannotCreateAnymore: "これ以上作成することはできません。"
|
youCannotCreateAnymore: "これ以上作成することはできません。"
|
||||||
cannotPerformTemporary: "一時的に利用できません"
|
cannotPerformTemporary: "一時的に利用できません"
|
||||||
cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。"
|
cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。"
|
||||||
|
@ -1496,6 +1497,7 @@ _role:
|
||||||
inviteLimitCycle: "招待コードの発行間隔"
|
inviteLimitCycle: "招待コードの発行間隔"
|
||||||
inviteExpirationTime: "招待コードの有効期限"
|
inviteExpirationTime: "招待コードの有効期限"
|
||||||
canManageCustomEmojis: "カスタム絵文字の管理"
|
canManageCustomEmojis: "カスタム絵文字の管理"
|
||||||
|
canManageAvatarDecorations: "アバターデコレーションの管理"
|
||||||
driveCapacity: "ドライブ容量"
|
driveCapacity: "ドライブ容量"
|
||||||
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
alwaysMarkNsfw: "ファイルにNSFWを常に付与"
|
||||||
pinMax: "ノートのピン留めの最大数"
|
pinMax: "ノートのピン留めの最大数"
|
||||||
|
|
|
@ -32,6 +32,7 @@ export type RolePolicies = {
|
||||||
inviteLimitCycle: number;
|
inviteLimitCycle: number;
|
||||||
inviteExpirationTime: number;
|
inviteExpirationTime: number;
|
||||||
canManageCustomEmojis: boolean;
|
canManageCustomEmojis: boolean;
|
||||||
|
canManageAvatarDecorations: boolean;
|
||||||
canSearchNotes: boolean;
|
canSearchNotes: boolean;
|
||||||
canUseTranslator: boolean;
|
canUseTranslator: boolean;
|
||||||
canHideAds: boolean;
|
canHideAds: boolean;
|
||||||
|
@ -57,6 +58,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
inviteLimitCycle: 60 * 24 * 7,
|
inviteLimitCycle: 60 * 24 * 7,
|
||||||
inviteExpirationTime: 0,
|
inviteExpirationTime: 0,
|
||||||
canManageCustomEmojis: false,
|
canManageCustomEmojis: false,
|
||||||
|
canManageAvatarDecorations: false,
|
||||||
canSearchNotes: false,
|
canSearchNotes: false,
|
||||||
canUseTranslator: true,
|
canUseTranslator: true,
|
||||||
canHideAds: false,
|
canHideAds: false,
|
||||||
|
@ -306,6 +308,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
|
inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
|
||||||
inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)),
|
inviteExpirationTime: calc('inviteExpirationTime', vs => Math.max(...vs)),
|
||||||
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
|
canManageCustomEmojis: calc('canManageCustomEmojis', vs => vs.some(v => v === true)),
|
||||||
|
canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)),
|
||||||
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
|
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
|
||||||
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
|
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
|
||||||
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
|
||||||
|
|
|
@ -11,7 +11,7 @@ export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireModerator: true,
|
requireRolePolicy: 'canManageAvatarDecorations',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
|
|
@ -13,8 +13,7 @@ export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireModerator: true,
|
requireRolePolicy: 'canManageAvatarDecorations',
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireModerator: true,
|
requireRolePolicy: 'canManageAvatarDecorations',
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireModerator: true,
|
requireRolePolicy: 'canManageAvatarDecorations',
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
},
|
},
|
||||||
|
|
|
@ -66,6 +66,7 @@ export const ROLE_POLICIES = [
|
||||||
'inviteLimitCycle',
|
'inviteLimitCycle',
|
||||||
'inviteExpirationTime',
|
'inviteExpirationTime',
|
||||||
'canManageCustomEmojis',
|
'canManageCustomEmojis',
|
||||||
|
'canManageAvatarDecorations',
|
||||||
'canSearchNotes',
|
'canSearchNotes',
|
||||||
'canUseTranslator',
|
'canUseTranslator',
|
||||||
'canHideAds',
|
'canHideAds',
|
||||||
|
|
|
@ -118,7 +118,7 @@ const menuDef = $computed(() => [{
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-sparkles',
|
icon: 'ti ti-sparkles',
|
||||||
text: i18n.ts.avatarDecorations,
|
text: i18n.ts.avatarDecorations,
|
||||||
to: '/admin/avatar-decorations',
|
to: '/avatar-decorations',
|
||||||
active: currentPage?.route.name === 'avatarDecorations',
|
active: currentPage?.route.name === 'avatarDecorations',
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-whirl',
|
icon: 'ti ti-whirl',
|
||||||
|
|
|
@ -259,6 +259,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageAvatarDecorations, 'canManageAvatarDecorations'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canManageAvatarDecorations }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canManageAvatarDecorations.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canManageAvatarDecorations.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canManageAvatarDecorations)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canManageAvatarDecorations.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canManageAvatarDecorations.value" :disabled="role.policies.canManageAvatarDecorations.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canManageAvatarDecorations.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canSearchNotes, 'canSearchNotes'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canSearchNotes, 'canSearchNotes'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canSearchNotes }}</template>
|
<template #label>{{ i18n.ts._role._options.canSearchNotes }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
|
|
|
@ -79,6 +79,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageAvatarDecorations, 'canManageAvatarDecorations'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canManageAvatarDecorations }}</template>
|
||||||
|
<template #suffix>{{ policies.canManageAvatarDecorations ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.canManageAvatarDecorations">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageCustomEmojis, 'canManageCustomEmojis'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageCustomEmojis, 'canManageCustomEmojis'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canManageCustomEmojis }}</template>
|
<template #label>{{ i18n.ts._role._options.canManageCustomEmojis }}</template>
|
||||||
<template #suffix>{{ policies.canManageCustomEmojis ? i18n.ts.yes : i18n.ts.no }}</template>
|
<template #suffix>{{ policies.canManageCustomEmojis ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="900">
|
<MkSpacer :contentMax="900">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkFolder v-for="avatarDecoration in avatarDecorations" :key="avatarDecoration.id ?? avatarDecoration._id" :defaultOpen="avatarDecoration.id == null">
|
<MkFolder v-for="avatarDecoration in avatarDecorations" :key="avatarDecoration.id ?? avatarDecoration._id" :defaultOpen="avatarDecoration.id == null">
|
||||||
|
@ -35,7 +35,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import { } from 'vue';
|
||||||
import XHeader from './_header_.vue';
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
|
@ -313,6 +313,10 @@ export const routes = [{
|
||||||
}, {
|
}, {
|
||||||
path: '/custom-emojis-manager',
|
path: '/custom-emojis-manager',
|
||||||
component: page(() => import('./pages/custom-emojis-manager.vue')),
|
component: page(() => import('./pages/custom-emojis-manager.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/avatar-decorations',
|
||||||
|
name: 'avatarDecorations',
|
||||||
|
component: page(() => import('./pages/avatar-decorations.vue')),
|
||||||
}, {
|
}, {
|
||||||
path: '/registry/keys/system/:path(*)?',
|
path: '/registry/keys/system/:path(*)?',
|
||||||
component: page(() => import('./pages/registry.keys.vue')),
|
component: page(() => import('./pages/registry.keys.vue')),
|
||||||
|
@ -350,7 +354,7 @@ export const routes = [{
|
||||||
}, {
|
}, {
|
||||||
path: '/avatar-decorations',
|
path: '/avatar-decorations',
|
||||||
name: 'avatarDecorations',
|
name: 'avatarDecorations',
|
||||||
component: page(() => import('./pages/admin/avatar-decorations.vue')),
|
component: page(() => import('./pages/avatar-decorations.vue')),
|
||||||
}, {
|
}, {
|
||||||
path: '/queue',
|
path: '/queue',
|
||||||
name: 'queue',
|
name: 'queue',
|
||||||
|
|
|
@ -67,6 +67,11 @@ export function openInstanceMenu(ev: MouseEvent) {
|
||||||
to: '/custom-emojis-manager',
|
to: '/custom-emojis-manager',
|
||||||
text: i18n.ts.manageCustomEmojis,
|
text: i18n.ts.manageCustomEmojis,
|
||||||
icon: 'ti ti-icons',
|
icon: 'ti ti-icons',
|
||||||
|
} : undefined, ($i && ($i.isAdmin || $i.policies.canManageAvatarDecorations)) ? {
|
||||||
|
type: 'link',
|
||||||
|
to: '/avatar-decorations',
|
||||||
|
text: i18n.ts.manageAvatarDecorations,
|
||||||
|
icon: 'ti ti-sparkles',
|
||||||
} : undefined],
|
} : undefined],
|
||||||
}, null, (instance.impressumUrl) ? {
|
}, null, (instance.impressumUrl) ? {
|
||||||
text: i18n.ts.impressum,
|
text: i18n.ts.impressum,
|
||||||
|
|
Loading…
Reference in a new issue