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 { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { deepClone } from '@/utility/clone.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<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', v: any): void;
|
(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>
|
</MkSwitch>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="D extends RolePolicyEditorItem">
|
<script lang="ts" setup generic="D extends RolePolicyDefItem">
|
||||||
import type { RolePolicyEditorItem, GetRolePolicyEditorValuesType } from '@/types/role-policy-editor.js';
|
import type { RolePolicyDefItem, GetRolePolicyEditorValuesType } from '@/types/role-policy-editor.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -108,7 +108,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const model = defineModel<GetRolePolicyEditorValuesType<D>>({ required: true });
|
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;
|
return props.def.type === type;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -10,13 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
<MkFolder v-for="(def, key) in filteredDefs" :key="key">
|
<MkFolder v-for="(def, key) in filteredDefs" :key="key">
|
||||||
<template #label>{{ def.folderLabel }}</template>
|
<template #label>{{ def.displayLabel }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<span v-if="withUseDefault && model[key].useDefault">{{ i18n.ts._role.useBaseValue }}</span>
|
<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>{{ getPolicyDisplayValue(def, 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-if="withPriority" :class="$style.priorityIndicator"><i :class="getPriorityIcon(model[key].priority)"></i></span>
|
<span v-if="withPriority" :class="$style.priorityIndicator"><i :class="getPriorityIcon(model[key].priority)"></i></span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -57,8 +54,8 @@ import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkRange from '@/components/MkRange.vue';
|
import MkRange from '@/components/MkRange.vue';
|
||||||
import XForm from './roles.policy-editor.form.vue';
|
import XForm from './roles.policy-editor.form.vue';
|
||||||
import { rolePolicyEditorDef } from './roles.policy-editor.def.js';
|
import { rolePolicyDef, getPolicyDisplayValue } from '@/utility/role-policy.js';
|
||||||
import type { GetRolePolicyEditorValuesType, RolePolicyEditorDef, RolePolicyEditorItemBaseFolderSuffixGetter } from '@/types/role-policy-editor.js';
|
import type { GetRolePolicyEditorValuesType, RolePolicyDefItem } from '@/types/role-policy-editor.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
withUseDefault: UD;
|
withUseDefault: UD;
|
||||||
|
@ -75,14 +72,14 @@ type RemoveNever<T> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type RolePolicyEditorValueItem = {
|
type RolePolicyEditorValueItem = {
|
||||||
value: GetRolePolicyEditorValuesType<typeof rolePolicyEditorDef[keyof typeof rolePolicyEditorDef]>;
|
value: GetRolePolicyEditorValuesType<typeof rolePolicyDef[keyof typeof rolePolicyDef]>;
|
||||||
} & RemoveNever<
|
} & RemoveNever<
|
||||||
(UD extends true ? { useDefault: boolean } : { useDefault: never })
|
(UD extends true ? { useDefault: boolean } : { useDefault: never })
|
||||||
& (WP extends true ? { priority: 0 | 1 | 2 } : { priority: never })
|
& (WP extends true ? { priority: 0 | 1 | 2 } : { priority: never })
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type RolePolicyEditorValue = {
|
type RolePolicyEditorValue = {
|
||||||
[K in keyof typeof rolePolicyEditorDef]: RolePolicyEditorValueItem;
|
[K in keyof typeof rolePolicyDef]: RolePolicyEditorValueItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
const model = defineModel<RolePolicyEditorValue>({ required: true });
|
const model = defineModel<RolePolicyEditorValue>({ required: true });
|
||||||
|
@ -95,15 +92,15 @@ function matchQuery(keywords: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredDefs = computed(() => {
|
const filteredDefs = computed(() => {
|
||||||
if (!props.withSearchBar) return rolePolicyEditorDef;
|
if (!props.withSearchBar) return rolePolicyDef;
|
||||||
|
|
||||||
return Object.fromEntries(
|
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;
|
if (searchQuery.value.trim().length === 0) return true;
|
||||||
const matchTerms = [
|
const matchTerms = [
|
||||||
key,
|
key,
|
||||||
def.folderLabel,
|
def.displayLabel,
|
||||||
...(def.searchTerms ?? []),
|
...((def as RolePolicyDefItem).searchTerms ? (def as RolePolicyDefItem).searchTerms ?? [] : []),
|
||||||
];
|
];
|
||||||
return matchQuery(matchTerms);
|
return matchQuery(matchTerms);
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
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 MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import XPolicyEditor from './roles.policy-editor.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>
|
<template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template>
|
||||||
|
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<div v-for="policy in Object.keys($i.policies)" :key="policy">
|
<MkKeyValue v-for="(value, pKey) in policiesKv" :key="pKey">
|
||||||
{{ policy }} ... {{ $i.policies[policy] }}
|
<template #key>{{ value.label }}</template>
|
||||||
</div>
|
<template #value>{{ value.value }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
|
@ -151,16 +152,15 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import FormSlot from '@/components/form/slot.vue';
|
import FormSlot from '@/components/form/slot.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||||
import FormSection from '@/components/form/section.vue';
|
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
import { signout } from '@/signout.js';
|
import { signout } from '@/signout.js';
|
||||||
import { migrateOldSettings } from '@/pref-migrate.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';
|
import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
@ -172,6 +172,22 @@ const devMode = prefer.model('devMode');
|
||||||
const stackingRouterView = prefer.model('experimental.stackingRouterView');
|
const stackingRouterView = prefer.model('experimental.stackingRouterView');
|
||||||
const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
|
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 () => {
|
watch(skipNoteRender, async () => {
|
||||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,24 +5,24 @@
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
import type { ROLE_POLICIES } from '@@/js/const.js';
|
import type { ROLE_POLICIES } from '@@/js/const.js';
|
||||||
|
|
||||||
interface RolePolicyEditorItemBase<T = unknown> {
|
interface RolePolicyDefItemBase<T extends unknown = any> {
|
||||||
searchTerms?: string[];
|
searchTerms?: string[];
|
||||||
type: string;
|
type: string;
|
||||||
folderLabel: string;
|
displayLabel: string;
|
||||||
folderSuffix?: string | ((value: T) => string);
|
displayValue?: string | ((value: T, full?: boolean) => string);
|
||||||
inputLabel?: string;
|
inputLabel?: string;
|
||||||
inputCaption?: string | Component;
|
inputCaption?: string | Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RolePolicyEditorItemBaseFolderSuffixGetter = {
|
export type RolePolicyDefItemBaseDisplayValueGetter = {
|
||||||
folderSuffix: (value: boolean | number | string) => string;
|
displayValue: (value: boolean | number | string, full?: boolean) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface RolePolicyEditorItemBoolean extends RolePolicyEditorItemBase<boolean> {
|
interface RolePolicyDefItemBoolean extends RolePolicyDefItemBase<boolean> {
|
||||||
type: 'boolean';
|
type: 'boolean';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RolePolicyEditorItemNumber extends RolePolicyEditorItemBase<number> {
|
interface RolePolicyDefItemNumber extends RolePolicyDefItemBase<number> {
|
||||||
type: 'number';
|
type: 'number';
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
|
@ -30,7 +30,7 @@ interface RolePolicyEditorItemNumber extends RolePolicyEditorItemBase<number> {
|
||||||
inputSuffix?: string;
|
inputSuffix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RolePolicyEditorItemRange extends RolePolicyEditorItemBase<number> {
|
interface RolePolicyDefItemRange extends RolePolicyDefItemBase<number> {
|
||||||
type: 'range';
|
type: 'range';
|
||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
|
@ -40,12 +40,12 @@ interface RolePolicyEditorItemRange extends RolePolicyEditorItemBase<number> {
|
||||||
inputSuffix?: string;
|
inputSuffix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RolePolicyEditorItemString extends RolePolicyEditorItemBase<string> {
|
interface RolePolicyDefItemString extends RolePolicyDefItemBase<string> {
|
||||||
type: 'string';
|
type: 'string';
|
||||||
multiline?: boolean;
|
multiline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RolePolicyEditorItemEnum extends RolePolicyEditorItemBase<string> {
|
interface RolePolicyDefItemEnum extends RolePolicyDefItemBase<string> {
|
||||||
type: 'enum';
|
type: 'enum';
|
||||||
enum: {
|
enum: {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -53,30 +53,30 @@ interface RolePolicyEditorItemEnum extends RolePolicyEditorItemBase<string> {
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RolePolicyEditorItem =
|
export type RolePolicyDefItem =
|
||||||
RolePolicyEditorItemBoolean |
|
RolePolicyDefItemBoolean |
|
||||||
RolePolicyEditorItemNumber |
|
RolePolicyDefItemNumber |
|
||||||
RolePolicyEditorItemRange |
|
RolePolicyDefItemRange |
|
||||||
RolePolicyEditorItemString |
|
RolePolicyDefItemString |
|
||||||
RolePolicyEditorItemEnum;
|
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> =
|
export type GetRolePolicyEditorValuesType<T extends RolePolicyDefItem> =
|
||||||
T extends RolePolicyEditorItemBoolean ? boolean :
|
T extends RolePolicyDefItemBoolean ? boolean :
|
||||||
T extends RolePolicyEditorItemNumber ? number :
|
T extends RolePolicyDefItemNumber ? number :
|
||||||
T extends RolePolicyEditorItemRange ? number :
|
T extends RolePolicyDefItemRange ? number :
|
||||||
T extends RolePolicyEditorItemString ? string :
|
T extends RolePolicyDefItemString ? string :
|
||||||
T extends RolePolicyEditorItemEnum ? T['enum'][number]['value'] :
|
T extends RolePolicyDefItemEnum ? T['enum'][number]['value'] :
|
||||||
never;
|
never;
|
||||||
|
|
||||||
export type RolePolicyValueRecord<T extends Record<string, RolePolicyEditorItem>> = {
|
export type RolePolicyValueRecord<T extends Record<string, RolePolicyDefItem>> = {
|
||||||
[K in keyof T]: {
|
[K in keyof T]: {
|
||||||
value: GetRolePolicyEditorValuesType<T[K]>;
|
value: GetRolePolicyEditorValuesType<T[K]>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RolePolicySettingsRecord<T extends Record<string, RolePolicyEditorItem>> = {
|
export type RolePolicySettingsRecord<T extends Record<string, RolePolicyDefItem>> = {
|
||||||
[K in keyof T]: {
|
[K in keyof T]: {
|
||||||
value: GetRolePolicyEditorValuesType<T[K]>;
|
value: GetRolePolicyEditorValuesType<T[K]>;
|
||||||
useDefault: boolean;
|
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