mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-27 21:04:37 +02:00
feat(frontend): ロール設定ページを追加
This commit is contained in:
@@ -1094,6 +1094,7 @@ likeOnlyForRemote: "全て (リモートはいいねのみ)"
|
||||
nonSensitiveOnly: "非センシティブのみ"
|
||||
nonSensitiveOnlyForLocalLikeOnlyForRemote: "非センシティブのみ (リモートはいいねのみ)"
|
||||
rolesAssignedToMe: "自分に割り当てられたロール"
|
||||
roleSettings: "ロール設定"
|
||||
resetPasswordConfirm: "パスワードリセットしますか?"
|
||||
sensitiveWords: "センシティブワード"
|
||||
sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
|
||||
|
||||
@@ -93,6 +93,11 @@ const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||
text: i18n.ts.privacy,
|
||||
to: '/settings/privacy',
|
||||
active: currentPage.value?.route.name === 'privacy',
|
||||
}, {
|
||||
icon: 'ti ti-badges',
|
||||
text: i18n.ts.roleSettings,
|
||||
to: '/settings/roles',
|
||||
active: currentPage.value?.route.name === 'roles',
|
||||
}, {
|
||||
icon: 'ti ti-bell',
|
||||
text: i18n.ts.notifications,
|
||||
|
||||
@@ -49,17 +49,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['roles']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-badges"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.rolesAssignedToMe }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['account', 'move', 'migration']">
|
||||
<MkFolder>
|
||||
<template #icon><SearchIcon><i class="ti ti-plane"></i></SearchIcon></template>
|
||||
@@ -171,7 +160,6 @@ import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.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 { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
|
||||
|
||||
@@ -51,41 +51,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['role', 'badge', 'visibility']">
|
||||
<FormSection>
|
||||
<template #label><SearchLabel>{{ i18n.ts._roleDisplay.title }}</SearchLabel></template>
|
||||
<template #description><SearchText>{{ i18n.ts._roleDisplay.description }}</SearchText></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkInfo v-if="roleDisplayRoles.length === 0">{{ i18n.ts._roleDisplay.noRoles }}</MkInfo>
|
||||
<MkSwitch
|
||||
v-for="role in roleDisplayRoles"
|
||||
:key="role.id"
|
||||
:modelValue="isRoleDisplayShown(role)"
|
||||
:disabled="role.isPublicDisplayRequired"
|
||||
@update:modelValue="value => updateRoleDisplay(role, value)"
|
||||
>
|
||||
<template #label>
|
||||
<SearchLabel>
|
||||
<span :class="$style.roleLabel" :style="{ '--color': role.color ?? '' }">
|
||||
<span :class="$style.roleIcon">
|
||||
<img v-if="role.iconUrl" :class="$style.roleBadge" :src="role.iconUrl"/>
|
||||
<i v-else-if="role.isAdministrator" class="ti ti-crown"></i>
|
||||
<i v-else-if="role.isModerator" class="ti ti-shield"></i>
|
||||
<i v-else class="ti ti-user"></i>
|
||||
</span>
|
||||
<span>{{ role.name }}</span>
|
||||
</span>
|
||||
</SearchLabel>
|
||||
</template>
|
||||
<template #caption>
|
||||
<SearchText v-if="role.isPublicDisplayRequired">{{ i18n.ts._roleDisplay.alwaysShownByAdmin }}</SearchText>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['crawle', 'index', 'search']">
|
||||
<MkSwitch v-model="noCrawle" @update:modelValue="save()">
|
||||
<template #label><SearchLabel>{{ i18n.ts.noCrawle }}</SearchLabel></template>
|
||||
@@ -248,7 +213,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { MkSelectItem } from '@/components/MkSelect.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
@@ -266,23 +230,9 @@ import * as os from '@/os.js';
|
||||
import MkDisableSection from '@/components/MkDisableSection.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||
import { updateCurrentAccount } from '@/accounts.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
type RoleDisplayRole = Misskey.entities.IResponse['roles'][number] & {
|
||||
isPublicDisplayRequired?: boolean;
|
||||
};
|
||||
type MeDetailedWithRoleDisplay = Misskey.entities.MeDetailed & {
|
||||
hiddenRoleIds?: string[];
|
||||
roles: RoleDisplayRole[];
|
||||
};
|
||||
type IUpdateWithHiddenRoleIdsRequest = Misskey.Endpoints['i/update']['req'] & {
|
||||
hiddenRoleIds: string[];
|
||||
};
|
||||
|
||||
const me = $i as MeDetailedWithRoleDisplay;
|
||||
|
||||
const isLocked = ref($i.isLocked);
|
||||
const autoAcceptFollowed = ref($i.autoAcceptFollowed);
|
||||
const noCrawle = ref($i.noCrawle);
|
||||
@@ -292,8 +242,6 @@ const requireSigninToViewContents = ref($i.requireSigninToViewContents ?? false)
|
||||
const makeNotesFollowersOnlyBefore = ref($i.makeNotesFollowersOnlyBefore ?? null);
|
||||
const makeNotesHiddenBefore = ref($i.makeNotesHiddenBefore ?? null);
|
||||
const hideOnlineStatus = ref($i.hideOnlineStatus);
|
||||
const hiddenRoleIds = ref([...getHiddenRoleIds(me)]);
|
||||
const roleDisplayRoles = computed(() => me.roles);
|
||||
const publicReactions = ref($i.publicReactions);
|
||||
const {
|
||||
model: followingVisibility,
|
||||
@@ -452,35 +400,6 @@ async function update_requireSigninToViewContents(value: boolean) {
|
||||
save();
|
||||
}
|
||||
|
||||
function getHiddenRoleIds(user: { hiddenRoleIds?: string[] }): string[] {
|
||||
return user.hiddenRoleIds ?? [];
|
||||
}
|
||||
|
||||
function isRoleDisplayShown(role: RoleDisplayRole): boolean {
|
||||
return role.isPublicDisplayRequired === true || !hiddenRoleIds.value.includes(role.id);
|
||||
}
|
||||
|
||||
async function updateRoleDisplay(role: RoleDisplayRole, visible: boolean) {
|
||||
if (role.isPublicDisplayRequired === true) return;
|
||||
|
||||
const nextHiddenRoleIds = new Set(hiddenRoleIds.value);
|
||||
if (visible) {
|
||||
nextHiddenRoleIds.delete(role.id);
|
||||
} else {
|
||||
nextHiddenRoleIds.add(role.id);
|
||||
}
|
||||
|
||||
const nextIds = roleDisplayRoles.value
|
||||
.filter(role => role.isPublicDisplayRequired !== true && nextHiddenRoleIds.has(role.id))
|
||||
.map(role => role.id);
|
||||
const updated = await misskeyApi<MeDetailedWithRoleDisplay, 'i/update', IUpdateWithHiddenRoleIdsRequest>('i/update', {
|
||||
hiddenRoleIds: nextIds,
|
||||
});
|
||||
|
||||
updateCurrentAccount(updated);
|
||||
hiddenRoleIds.value = [...getHiddenRoleIds(updated)];
|
||||
}
|
||||
|
||||
function save() {
|
||||
misskeyApi('i/update', {
|
||||
isLocked: !!isLocked.value,
|
||||
@@ -508,24 +427,3 @@ definePage(() => ({
|
||||
icon: 'ti ti-lock-open',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.roleLabel {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--color, inherit);
|
||||
}
|
||||
|
||||
.roleIcon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 1.3em;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.roleBadge {
|
||||
height: 1.3em;
|
||||
vertical-align: -20%;
|
||||
}
|
||||
</style>
|
||||
|
||||
100
packages/frontend/src/pages/settings/roles.vue
Normal file
100
packages/frontend/src/pages/settings/roles.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<SearchMarker path="/settings/roles" :label="i18n.ts.roleSettings" :keywords="['role', 'badge']" icon="ti ti-badges">
|
||||
<div class="_gaps_m">
|
||||
<SearchMarker :keywords="['roles']">
|
||||
<div class="_gaps_s">
|
||||
<MkInfo v-if="roleDisplayRoles.length === 0">{{ i18n.ts._roleDisplay.noRoles }}</MkInfo>
|
||||
<div v-for="role in roleDisplayRoles" :key="role.id" :class="$style.roleItem">
|
||||
<MkRolePreview :role="role" :forModeration="false"/>
|
||||
<MkSwitch
|
||||
:modelValue="isRoleDisplayShown(role)"
|
||||
:disabled="role.isPublicDisplayRequired"
|
||||
@update:modelValue="value => updateRoleDisplay(role, value)"
|
||||
>
|
||||
<template #label>{{ i18n.ts._roleDisplay.title }}</template>
|
||||
<template #caption v-if="role.isPublicDisplayRequired">{{ i18n.ts._roleDisplay.alwaysShownByAdmin }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import type * as Misskey from 'misskey-js';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { updateCurrentAccount } from '@/accounts.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
type RoleDisplayRole = Misskey.entities.IResponse['roles'][number] & {
|
||||
isPublicDisplayRequired?: boolean;
|
||||
};
|
||||
type MeDetailedWithRoleDisplay = Misskey.entities.MeDetailed & {
|
||||
hiddenRoleIds?: string[];
|
||||
roles: RoleDisplayRole[];
|
||||
};
|
||||
type IUpdateWithHiddenRoleIdsRequest = Misskey.Endpoints['i/update']['req'] & {
|
||||
hiddenRoleIds: string[];
|
||||
};
|
||||
|
||||
const me = $i as MeDetailedWithRoleDisplay;
|
||||
|
||||
const hiddenRoleIds = ref([...getHiddenRoleIds(me)]);
|
||||
const roleDisplayRoles = computed(() => me.roles);
|
||||
|
||||
function getHiddenRoleIds(user: { hiddenRoleIds?: string[] }): string[] {
|
||||
return user.hiddenRoleIds ?? [];
|
||||
}
|
||||
|
||||
function isRoleDisplayShown(role: RoleDisplayRole): boolean {
|
||||
return role.isPublicDisplayRequired === true || !hiddenRoleIds.value.includes(role.id);
|
||||
}
|
||||
|
||||
async function updateRoleDisplay(role: RoleDisplayRole, visible: boolean) {
|
||||
if (role.isPublicDisplayRequired === true) return;
|
||||
|
||||
const nextHiddenRoleIds = new Set(hiddenRoleIds.value);
|
||||
if (visible) {
|
||||
nextHiddenRoleIds.delete(role.id);
|
||||
} else {
|
||||
nextHiddenRoleIds.add(role.id);
|
||||
}
|
||||
|
||||
const nextIds = roleDisplayRoles.value
|
||||
.filter(role => role.isPublicDisplayRequired !== true && nextHiddenRoleIds.has(role.id))
|
||||
.map(role => role.id);
|
||||
const updated = await misskeyApi<MeDetailedWithRoleDisplay, 'i/update', IUpdateWithHiddenRoleIdsRequest>('i/update', {
|
||||
hiddenRoleIds: nextIds,
|
||||
});
|
||||
|
||||
updateCurrentAccount(updated);
|
||||
hiddenRoleIds.value = [...getHiddenRoleIds(updated)];
|
||||
}
|
||||
|
||||
definePage(() => ({
|
||||
title: i18n.ts.roleSettings,
|
||||
icon: 'ti ti-badges',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.roleItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -88,6 +88,10 @@ export const ROUTE_DEF = [{
|
||||
path: '/privacy',
|
||||
name: 'privacy',
|
||||
component: page(() => import('@/pages/settings/privacy.vue')),
|
||||
}, {
|
||||
path: '/roles',
|
||||
name: 'roles',
|
||||
component: page(() => import('@/pages/settings/roles.vue')),
|
||||
}, {
|
||||
path: '/emoji-palette',
|
||||
name: 'emoji-palette',
|
||||
|
||||
@@ -4388,6 +4388,10 @@ export interface Locale extends ILocale {
|
||||
* 自分に割り当てられたロール
|
||||
*/
|
||||
"rolesAssignedToMe": string;
|
||||
/**
|
||||
* ロール設定
|
||||
*/
|
||||
"roleSettings": string;
|
||||
/**
|
||||
* パスワードリセットしますか?
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user