enhance: ユーザーのロール一覧画面の多言語対応
This commit is contained in:
parent
7f4280f9d5
commit
a0a0c8a4ed
|
@ -107,7 +107,7 @@ import FormSlot from '@/components/form/slot.vue';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import type { RolePolicySettingsRecord } from './roles.policy-editor.def.js';
|
||||
import type { RolePolicySettingsRecord } from '@/utility/role-policy.js';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update:modelValue', v: any): void;
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { i18n } from '@/i18n.js';
|
||||
import XUploadableFileTypesCaption from './roles.policy-editor.uploadableFileTypesCaption.vue';
|
||||
import type {
|
||||
RolePolicyEditorDef,
|
||||
GetRolePolicyEditorValuesType,
|
||||
RolePolicyValueRecord as _RolePolicyValueRecord,
|
||||
RolePolicySettingsRecord as _RolePolicySettingsRecord,
|
||||
} from '@/types/role-policy-editor.js';
|
||||
|
||||
export const rolePolicyEditorDef = {
|
||||
rateLimitFactor: {
|
||||
type: 'range',
|
||||
folderLabel: i18n.ts._role._options.rateLimitFactor,
|
||||
folderSuffix: (value) => `${Math.round(value * 100)}%`,
|
||||
min: 0.3,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
textConverter: (value) => `${Math.round(value * 100)}%`,
|
||||
inputCaption: i18n.ts._role._options.descriptionOfRateLimitFactor,
|
||||
},
|
||||
gtlAvailable: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.gtlAvailable,
|
||||
},
|
||||
ltlAvailable: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.ltlAvailable,
|
||||
},
|
||||
canPublicNote: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canPublicNote,
|
||||
},
|
||||
chatAvailability: {
|
||||
type: 'enum',
|
||||
folderLabel: i18n.ts._role._options.chatAvailability,
|
||||
enum: [
|
||||
{ label: i18n.ts.enabled, value: 'available' },
|
||||
{ label: i18n.ts.readonly, value: 'readonly' },
|
||||
{ label: i18n.ts.disabled, value: 'unavailable' },
|
||||
],
|
||||
},
|
||||
mentionLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.mentionMax,
|
||||
},
|
||||
canInvite: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canInvite,
|
||||
},
|
||||
inviteLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.inviteLimit,
|
||||
},
|
||||
inviteLimitCycle: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.inviteLimitCycle,
|
||||
folderSuffix: (value) => `${value} ${i18n.ts._time.minute}`,
|
||||
inputSuffix: i18n.ts._time.minute,
|
||||
},
|
||||
inviteExpirationTime: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.inviteExpirationTime,
|
||||
folderSuffix: (value) => `${value} ${i18n.ts._time.minute}`,
|
||||
inputSuffix: i18n.ts._time.minute,
|
||||
},
|
||||
canManageAvatarDecorations: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canManageAvatarDecorations,
|
||||
},
|
||||
canManageCustomEmojis: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canManageCustomEmojis,
|
||||
},
|
||||
canSearchNotes: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canSearchNotes,
|
||||
},
|
||||
canUseTranslator: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canUseTranslator,
|
||||
},
|
||||
driveCapacityMb: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.driveCapacity,
|
||||
folderSuffix: (value) => `${value} MB`,
|
||||
inputSuffix: 'MB',
|
||||
},
|
||||
maxFileSizeMb: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.maxFileSize,
|
||||
folderSuffix: (value) => `${value} MB`,
|
||||
inputSuffix: 'MB',
|
||||
},
|
||||
uploadableFileTypes: {
|
||||
type: 'string',
|
||||
multiline: true,
|
||||
folderLabel: i18n.ts._role._options.uploadableFileTypes,
|
||||
folderSuffix: '...',
|
||||
inputCaption: XUploadableFileTypesCaption,
|
||||
},
|
||||
alwaysMarkNsfw: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.alwaysMarkNsfw,
|
||||
},
|
||||
canUpdateBioMedia: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canUpdateBioMedia,
|
||||
},
|
||||
pinLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.pinMax,
|
||||
},
|
||||
antennaLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.antennaMax,
|
||||
},
|
||||
wordMuteLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.wordMuteMax,
|
||||
inputSuffix: 'chars',
|
||||
},
|
||||
webhookLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.webhookMax,
|
||||
},
|
||||
clipLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.clipMax,
|
||||
},
|
||||
noteEachClipsLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.noteEachClipsMax,
|
||||
},
|
||||
userListLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.userListMax,
|
||||
},
|
||||
userEachUserListsLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.userEachUserListsMax,
|
||||
},
|
||||
canHideAds: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canHideAds,
|
||||
},
|
||||
avatarDecorationLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.avatarDecorationLimit,
|
||||
min: 0,
|
||||
max: 16,
|
||||
},
|
||||
canImportAntennas: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canImportAntennas,
|
||||
},
|
||||
canImportBlocking: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canImportBlocking,
|
||||
},
|
||||
canImportFollowing: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canImportFollowing,
|
||||
},
|
||||
canImportMuting: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canImportMuting,
|
||||
},
|
||||
canImportUserLists: {
|
||||
type: 'boolean',
|
||||
folderLabel: i18n.ts._role._options.canImportUserLists,
|
||||
},
|
||||
noteDraftLimit: {
|
||||
type: 'number',
|
||||
folderLabel: i18n.ts._role._options.noteDraftLimit,
|
||||
min: 0,
|
||||
},
|
||||
} satisfies RolePolicyEditorDef;
|
||||
|
||||
export type RolePolicyValueRecord = _RolePolicyValueRecord<typeof rolePolicyEditorDef>;
|
||||
|
||||
export type RolePolicySettingsRecord = _RolePolicySettingsRecord<typeof rolePolicyEditorDef>;
|
||||
|
||||
export type RolePolicyRecord = {
|
||||
[K in keyof typeof rolePolicyEditorDef]: GetRolePolicyEditorValuesType<typeof rolePolicyEditorDef[K]>;
|
||||
};
|
|
@ -90,8 +90,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSwitch>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup generic="D extends RolePolicyEditorItem">
|
||||
import type { RolePolicyEditorItem, GetRolePolicyEditorValuesType } from '@/types/role-policy-editor.js';
|
||||
<script lang="ts" setup generic="D extends RolePolicyDefItem">
|
||||
import type { RolePolicyDefItem, GetRolePolicyEditorValuesType } from '@/types/role-policy-editor.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
|
@ -108,7 +108,7 @@ const props = defineProps<{
|
|||
|
||||
const model = defineModel<GetRolePolicyEditorValuesType<D>>({ required: true });
|
||||
|
||||
function assertModelType<T extends RolePolicyEditorItem['type']>(m: unknown, type: T): m is GetRolePolicyEditorValuesType<Extract<RolePolicyEditorItem, { type: T }>> {
|
||||
function assertModelType<T extends RolePolicyDefItem['type']>(m: unknown, type: T): m is GetRolePolicyEditorValuesType<Extract<RolePolicyDefItem, { type: T }>> {
|
||||
return props.def.type === type;
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -10,13 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkInput>
|
||||
|
||||
<MkFolder v-for="(def, key) in filteredDefs" :key="key">
|
||||
<template #label>{{ def.folderLabel }}</template>
|
||||
<template #label>{{ def.displayLabel }}</template>
|
||||
<template #suffix>
|
||||
<span v-if="withUseDefault && model[key].useDefault">{{ i18n.ts._role.useBaseValue }}</span>
|
||||
<span v-else-if="'folderSuffix' in def">{{ typeof def.folderSuffix === 'string' ? def.folderSuffix : (def as RolePolicyEditorItemBaseFolderSuffixGetter).folderSuffix(model[key].value) }}</span>
|
||||
<span v-else-if="def.type === 'boolean'">{{ model[key].value === true ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||
<span v-else-if="def.type === 'enum'">{{ def.enum.find((v) => v.value === model[key].value)?.label ?? model[key].value }}</span>
|
||||
<span v-else>{{ model[key].value }}</span>
|
||||
<span v-else>{{ getPolicyDisplayValue(def, model[key].value) }}</span>
|
||||
<span v-if="withPriority" :class="$style.priorityIndicator"><i :class="getPriorityIcon(model[key].priority)"></i></span>
|
||||
</template>
|
||||
|
||||
|
@ -57,8 +54,8 @@ import MkFolder from '@/components/MkFolder.vue';
|
|||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import XForm from './roles.policy-editor.form.vue';
|
||||
import { rolePolicyEditorDef } from './roles.policy-editor.def.js';
|
||||
import type { GetRolePolicyEditorValuesType, RolePolicyEditorDef, RolePolicyEditorItemBaseFolderSuffixGetter } from '@/types/role-policy-editor.js';
|
||||
import { rolePolicyDef, getPolicyDisplayValue } from '@/utility/role-policy.js';
|
||||
import type { GetRolePolicyEditorValuesType, RolePolicyDefItem } from '@/types/role-policy-editor.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
withUseDefault: UD;
|
||||
|
@ -75,14 +72,14 @@ type RemoveNever<T> = {
|
|||
};
|
||||
|
||||
type RolePolicyEditorValueItem = {
|
||||
value: GetRolePolicyEditorValuesType<typeof rolePolicyEditorDef[keyof typeof rolePolicyEditorDef]>;
|
||||
value: GetRolePolicyEditorValuesType<typeof rolePolicyDef[keyof typeof rolePolicyDef]>;
|
||||
} & RemoveNever<
|
||||
(UD extends true ? { useDefault: boolean } : { useDefault: never })
|
||||
& (WP extends true ? { priority: 0 | 1 | 2 } : { priority: never })
|
||||
>;
|
||||
|
||||
type RolePolicyEditorValue = {
|
||||
[K in keyof typeof rolePolicyEditorDef]: RolePolicyEditorValueItem;
|
||||
[K in keyof typeof rolePolicyDef]: RolePolicyEditorValueItem;
|
||||
};
|
||||
|
||||
const model = defineModel<RolePolicyEditorValue>({ required: true });
|
||||
|
@ -95,15 +92,15 @@ function matchQuery(keywords: string[]) {
|
|||
}
|
||||
|
||||
const filteredDefs = computed(() => {
|
||||
if (!props.withSearchBar) return rolePolicyEditorDef;
|
||||
if (!props.withSearchBar) return rolePolicyDef;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(rolePolicyEditorDef as RolePolicyEditorDef).filter(([key, def]) => {
|
||||
Object.entries(rolePolicyDef).filter(([key, def]) => {
|
||||
if (searchQuery.value.trim().length === 0) return true;
|
||||
const matchTerms = [
|
||||
key,
|
||||
def.folderLabel,
|
||||
...(def.searchTerms ?? []),
|
||||
def.displayLabel,
|
||||
...((def as RolePolicyDefItem).searchTerms ? (def as RolePolicyDefItem).searchTerms ?? [] : []),
|
||||
];
|
||||
return matchQuery(matchTerms);
|
||||
}),
|
||||
|
|
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import type { RolePolicyRecord, RolePolicyValueRecord } from './roles.policy-editor.def.js';
|
||||
import type { RolePolicyRecord, RolePolicyValueRecord } from '@/utility/role-policy.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import XPolicyEditor from './roles.policy-editor.vue';
|
||||
|
|
|
@ -39,9 +39,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<div v-for="policy in Object.keys($i.policies)" :key="policy">
|
||||
{{ policy }} ... {{ $i.policies[policy] }}
|
||||
</div>
|
||||
<MkKeyValue v-for="(value, pKey) in policiesKv" :key="pKey">
|
||||
<template #key>{{ value.label }}</template>
|
||||
<template #value>{{ value.value }}</template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
@ -151,16 +152,15 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
|
|||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSlot from '@/components/form/slot.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
import { signout } from '@/signout.js';
|
||||
import { migrateOldSettings } from '@/pref-migrate.js';
|
||||
import { rolePolicyDef, getPolicyDisplayValue } from '@/utility/role-policy.js';
|
||||
import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
@ -172,6 +172,22 @@ const devMode = prefer.model('devMode');
|
|||
const stackingRouterView = prefer.model('experimental.stackingRouterView');
|
||||
const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
|
||||
|
||||
const policiesKv = computed(() => {
|
||||
return Object.fromEntries(
|
||||
Object.entries($i.policies).map(([key, def]) => {
|
||||
const _key = key as keyof typeof $i.policies | keyof typeof rolePolicyDef;
|
||||
const value = getPolicyDisplayValue(rolePolicyDef[_key], def, true);
|
||||
return [_key, {
|
||||
label: rolePolicyDef[_key]?.displayLabel ?? _key,
|
||||
value,
|
||||
}];
|
||||
}),
|
||||
) as Record<keyof typeof $i.policies, {
|
||||
label: string;
|
||||
value: string;
|
||||
}>;
|
||||
});
|
||||
|
||||
watch(skipNoteRender, async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
|
|
@ -5,24 +5,24 @@
|
|||
import type { Component } from 'vue';
|
||||
import type { ROLE_POLICIES } from '@@/js/const.js';
|
||||
|
||||
interface RolePolicyEditorItemBase<T = unknown> {
|
||||
interface RolePolicyDefItemBase<T extends unknown = any> {
|
||||
searchTerms?: string[];
|
||||
type: string;
|
||||
folderLabel: string;
|
||||
folderSuffix?: string | ((value: T) => string);
|
||||
displayLabel: string;
|
||||
displayValue?: string | ((value: T, full?: boolean) => string);
|
||||
inputLabel?: string;
|
||||
inputCaption?: string | Component;
|
||||
}
|
||||
|
||||
export type RolePolicyEditorItemBaseFolderSuffixGetter = {
|
||||
folderSuffix: (value: boolean | number | string) => string;
|
||||
export type RolePolicyDefItemBaseDisplayValueGetter = {
|
||||
displayValue: (value: boolean | number | string, full?: boolean) => string;
|
||||
};
|
||||
|
||||
interface RolePolicyEditorItemBoolean extends RolePolicyEditorItemBase<boolean> {
|
||||
interface RolePolicyDefItemBoolean extends RolePolicyDefItemBase<boolean> {
|
||||
type: 'boolean';
|
||||
}
|
||||
|
||||
interface RolePolicyEditorItemNumber extends RolePolicyEditorItemBase<number> {
|
||||
interface RolePolicyDefItemNumber extends RolePolicyDefItemBase<number> {
|
||||
type: 'number';
|
||||
min?: number;
|
||||
max?: number;
|
||||
|
@ -30,7 +30,7 @@ interface RolePolicyEditorItemNumber extends RolePolicyEditorItemBase<number> {
|
|||
inputSuffix?: string;
|
||||
}
|
||||
|
||||
interface RolePolicyEditorItemRange extends RolePolicyEditorItemBase<number> {
|
||||
interface RolePolicyDefItemRange extends RolePolicyDefItemBase<number> {
|
||||
type: 'range';
|
||||
min: number;
|
||||
max: number;
|
||||
|
@ -40,12 +40,12 @@ interface RolePolicyEditorItemRange extends RolePolicyEditorItemBase<number> {
|
|||
inputSuffix?: string;
|
||||
}
|
||||
|
||||
interface RolePolicyEditorItemString extends RolePolicyEditorItemBase<string> {
|
||||
interface RolePolicyDefItemString extends RolePolicyDefItemBase<string> {
|
||||
type: 'string';
|
||||
multiline?: boolean;
|
||||
}
|
||||
|
||||
interface RolePolicyEditorItemEnum extends RolePolicyEditorItemBase<string> {
|
||||
interface RolePolicyDefItemEnum extends RolePolicyDefItemBase<string> {
|
||||
type: 'enum';
|
||||
enum: {
|
||||
label: string;
|
||||
|
@ -53,30 +53,30 @@ interface RolePolicyEditorItemEnum extends RolePolicyEditorItemBase<string> {
|
|||
}[];
|
||||
}
|
||||
|
||||
export type RolePolicyEditorItem =
|
||||
RolePolicyEditorItemBoolean |
|
||||
RolePolicyEditorItemNumber |
|
||||
RolePolicyEditorItemRange |
|
||||
RolePolicyEditorItemString |
|
||||
RolePolicyEditorItemEnum;
|
||||
export type RolePolicyDefItem =
|
||||
RolePolicyDefItemBoolean |
|
||||
RolePolicyDefItemNumber |
|
||||
RolePolicyDefItemRange |
|
||||
RolePolicyDefItemString |
|
||||
RolePolicyDefItemEnum;
|
||||
|
||||
export type RolePolicyEditorDef = Record<typeof ROLE_POLICIES[number], RolePolicyEditorItem>;
|
||||
export type RolePolicyDef = Record<typeof ROLE_POLICIES[number], RolePolicyDefItem>;
|
||||
|
||||
export type GetRolePolicyEditorValuesType<T extends RolePolicyEditorItem> =
|
||||
T extends RolePolicyEditorItemBoolean ? boolean :
|
||||
T extends RolePolicyEditorItemNumber ? number :
|
||||
T extends RolePolicyEditorItemRange ? number :
|
||||
T extends RolePolicyEditorItemString ? string :
|
||||
T extends RolePolicyEditorItemEnum ? T['enum'][number]['value'] :
|
||||
export type GetRolePolicyEditorValuesType<T extends RolePolicyDefItem> =
|
||||
T extends RolePolicyDefItemBoolean ? boolean :
|
||||
T extends RolePolicyDefItemNumber ? number :
|
||||
T extends RolePolicyDefItemRange ? number :
|
||||
T extends RolePolicyDefItemString ? string :
|
||||
T extends RolePolicyDefItemEnum ? T['enum'][number]['value'] :
|
||||
never;
|
||||
|
||||
export type RolePolicyValueRecord<T extends Record<string, RolePolicyEditorItem>> = {
|
||||
export type RolePolicyValueRecord<T extends Record<string, RolePolicyDefItem>> = {
|
||||
[K in keyof T]: {
|
||||
value: GetRolePolicyEditorValuesType<T[K]>;
|
||||
};
|
||||
};
|
||||
|
||||
export type RolePolicySettingsRecord<T extends Record<string, RolePolicyEditorItem>> = {
|
||||
export type RolePolicySettingsRecord<T extends Record<string, RolePolicyDefItem>> = {
|
||||
[K in keyof T]: {
|
||||
value: GetRolePolicyEditorValuesType<T[K]>;
|
||||
useDefault: boolean;
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import type {
|
||||
RolePolicyDef,
|
||||
GetRolePolicyEditorValuesType,
|
||||
RolePolicyValueRecord as _RolePolicyValueRecord,
|
||||
RolePolicySettingsRecord as _RolePolicySettingsRecord,
|
||||
} from '@/types/role-policy-editor.js';
|
||||
|
||||
export const rolePolicyDef = {
|
||||
rateLimitFactor: {
|
||||
type: 'range',
|
||||
displayLabel: i18n.ts._role._options.rateLimitFactor,
|
||||
displayValue: (value) => `${Math.round(value * 100)}%`,
|
||||
min: 0.3,
|
||||
max: 3,
|
||||
step: 0.1,
|
||||
textConverter: (value) => `${Math.round(value * 100)}%`,
|
||||
inputCaption: i18n.ts._role._options.descriptionOfRateLimitFactor,
|
||||
},
|
||||
gtlAvailable: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.gtlAvailable,
|
||||
},
|
||||
ltlAvailable: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.ltlAvailable,
|
||||
},
|
||||
canPublicNote: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canPublicNote,
|
||||
},
|
||||
chatAvailability: {
|
||||
type: 'enum',
|
||||
displayLabel: i18n.ts._role._options.chatAvailability,
|
||||
enum: [
|
||||
{ label: i18n.ts.enabled, value: 'available' as const },
|
||||
{ label: i18n.ts.readonly, value: 'readonly' as const },
|
||||
{ label: i18n.ts.disabled, value: 'unavailable' as const },
|
||||
],
|
||||
},
|
||||
mentionLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.mentionMax,
|
||||
},
|
||||
canInvite: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canInvite,
|
||||
},
|
||||
inviteLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.inviteLimit,
|
||||
},
|
||||
inviteLimitCycle: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.inviteLimitCycle,
|
||||
displayValue: (value) => `${value} ${i18n.ts._time.minute}`,
|
||||
inputSuffix: i18n.ts._time.minute,
|
||||
},
|
||||
inviteExpirationTime: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.inviteExpirationTime,
|
||||
displayValue: (value) => `${value} ${i18n.ts._time.minute}`,
|
||||
inputSuffix: i18n.ts._time.minute,
|
||||
},
|
||||
canManageAvatarDecorations: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canManageAvatarDecorations,
|
||||
},
|
||||
canManageCustomEmojis: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canManageCustomEmojis,
|
||||
},
|
||||
canSearchNotes: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canSearchNotes,
|
||||
},
|
||||
canUseTranslator: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canUseTranslator,
|
||||
},
|
||||
driveCapacityMb: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.driveCapacity,
|
||||
displayValue: (value) => `${value} MB`,
|
||||
inputSuffix: 'MB',
|
||||
},
|
||||
maxFileSizeMb: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.maxFileSize,
|
||||
displayValue: (value) => `${value} MB`,
|
||||
inputSuffix: 'MB',
|
||||
},
|
||||
uploadableFileTypes: {
|
||||
type: 'string',
|
||||
multiline: true,
|
||||
displayLabel: i18n.ts._role._options.uploadableFileTypes,
|
||||
displayValue: (value, full) => full === true ? Array.isArray(value) ? value.join(', ') : value : '...',
|
||||
inputCaption: defineAsyncComponent(() => import('@/pages/admin/roles.policy-editor.uploadableFileTypesCaption.vue')),
|
||||
},
|
||||
alwaysMarkNsfw: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.alwaysMarkNsfw,
|
||||
},
|
||||
canUpdateBioMedia: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canUpdateBioMedia,
|
||||
},
|
||||
pinLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.pinMax,
|
||||
},
|
||||
antennaLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.antennaMax,
|
||||
},
|
||||
wordMuteLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.wordMuteMax,
|
||||
inputSuffix: 'chars',
|
||||
},
|
||||
webhookLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.webhookMax,
|
||||
},
|
||||
clipLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.clipMax,
|
||||
},
|
||||
noteEachClipsLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.noteEachClipsMax,
|
||||
},
|
||||
userListLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.userListMax,
|
||||
},
|
||||
userEachUserListsLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.userEachUserListsMax,
|
||||
},
|
||||
canHideAds: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canHideAds,
|
||||
},
|
||||
avatarDecorationLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.avatarDecorationLimit,
|
||||
min: 0,
|
||||
max: 16,
|
||||
},
|
||||
canImportAntennas: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canImportAntennas,
|
||||
},
|
||||
canImportBlocking: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canImportBlocking,
|
||||
},
|
||||
canImportFollowing: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canImportFollowing,
|
||||
},
|
||||
canImportMuting: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canImportMuting,
|
||||
},
|
||||
canImportUserLists: {
|
||||
type: 'boolean',
|
||||
displayLabel: i18n.ts._role._options.canImportUserLists,
|
||||
},
|
||||
noteDraftLimit: {
|
||||
type: 'number',
|
||||
displayLabel: i18n.ts._role._options.noteDraftLimit,
|
||||
min: 0,
|
||||
},
|
||||
} satisfies RolePolicyDef;
|
||||
|
||||
export type RolePolicyValueRecord = _RolePolicyValueRecord<typeof rolePolicyDef>;
|
||||
|
||||
export type RolePolicySettingsRecord = _RolePolicySettingsRecord<typeof rolePolicyDef>;
|
||||
|
||||
export type RolePolicyRecord = {
|
||||
[K in keyof typeof rolePolicyDef]: GetRolePolicyEditorValuesType<typeof rolePolicyDef[K]>;
|
||||
};
|
||||
|
||||
type RolePolicyDefDef = typeof rolePolicyDef[keyof typeof rolePolicyDef];
|
||||
|
||||
export function getPolicyDisplayValue<D extends RolePolicyDefDef, T = GetRolePolicyEditorValuesType<D>>(
|
||||
policyDef: D,
|
||||
value: T,
|
||||
full = false,
|
||||
): string {
|
||||
if ('displayValue' in policyDef && policyDef.displayValue != null) {
|
||||
if (typeof policyDef.displayValue === 'string') {
|
||||
return policyDef.displayValue;
|
||||
} else {
|
||||
return policyDef.displayValue(value as never, full);
|
||||
}
|
||||
}
|
||||
|
||||
if (policyDef.type === 'enum') {
|
||||
const enumItem = policyDef.enum.find(item => item.value === value);
|
||||
if (enumItem) {
|
||||
return enumItem.label;
|
||||
} else {
|
||||
return value as string;
|
||||
}
|
||||
} else if (policyDef.type === 'boolean') {
|
||||
return value ? i18n.ts.yes : i18n.ts.no;
|
||||
} else if (typeof value === 'number') {
|
||||
return value.toString();
|
||||
} else if (typeof value === 'string') {
|
||||
return value;
|
||||
} else {
|
||||
return i18n.ts.unknown;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue