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:
GrapeApple0 2023-10-30 15:33:15 +09:00 committed by GitHub
parent 52dbab56a4
commit 4f180ad45c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 52 additions and 9 deletions

2
locales/index.d.ts vendored
View file

@ -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;

View file

@ -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: "ノートのピン留めの最大数"

View file

@ -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)),

View file

@ -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 = {

View file

@ -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;

View file

@ -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',

View file

@ -13,7 +13,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requireModerator: true, requireRolePolicy: 'canManageAvatarDecorations',
errors: { errors: {
}, },

View file

@ -66,6 +66,7 @@ export const ROLE_POLICIES = [
'inviteLimitCycle', 'inviteLimitCycle',
'inviteExpirationTime', 'inviteExpirationTime',
'canManageCustomEmojis', 'canManageCustomEmojis',
'canManageAvatarDecorations',
'canSearchNotes', 'canSearchNotes',
'canUseTranslator', 'canUseTranslator',
'canHideAds', 'canHideAds',

View file

@ -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',

View file

@ -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>

View file

@ -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>

View file

@ -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';

View file

@ -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',

View file

@ -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,