Skip to content

Commit bb913ee

Browse files
authored
feat(reaction): ロールでリアクションの制御をできるように (#660)
1 parent 5ab83ff commit bb913ee

File tree

8 files changed

+40
-7
lines changed

8 files changed

+40
-7
lines changed

locales/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6852,6 +6852,10 @@ export interface Locale extends ILocale {
68526852
* サウンド設定でドライブのファイルを利用
68536853
*/
68546854
"canUseDriveFileInSoundSettings": string;
6855+
/**
6856+
* リアクションの利用
6857+
*/
6858+
"canUseReaction": string;
68556859
/**
68566860
* アイコンデコレーションの最大取付個数
68576861
*/

locales/ja-JP.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,7 @@ _role:
17681768
canSearchNotes: "ノート検索の利用"
17691769
canUseTranslator: "翻訳機能の利用"
17701770
canUseDriveFileInSoundSettings: "サウンド設定でドライブのファイルを利用"
1771+
canUseReaction: "リアクションの利用"
17711772
avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
17721773
_condition:
17731774
roleAssignedTo: "マニュアルロールにアサイン済み"

packages/backend/src/core/ReactionService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ export class ReactionService {
116116
if (!await this.noteEntityService.isVisibleForMe(note, user.id)) {
117117
throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
118118
}
119+
const policies = await this.roleService.getUserPolicies(user.id);
119120

120121
let reaction = _reaction ?? FALLBACK;
121-
122-
if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
122+
if (note.reactionAcceptance === 'likeOnly' || !policies.canUseReaction || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
123123
reaction = '\u2764';
124124
} else if (_reaction) {
125125
const custom = reaction.match(isCustomEmojiRegexp);

packages/backend/src/core/RoleService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export type RolePolicies = {
5353
canSearchNotes: boolean;
5454
canUseTranslator: boolean;
5555
canUseDriveFileInSoundSettings: boolean;
56+
canUseReaction: boolean;
5657
canHideAds: boolean;
5758
driveCapacityMb: number;
5859
alwaysMarkNsfw: boolean;
@@ -91,6 +92,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
9192
canSearchNotes: false,
9293
canUseTranslator: true,
9394
canUseDriveFileInSoundSettings: false,
95+
canUseReaction: true,
9496
canHideAds: false,
9597
driveCapacityMb: 100,
9698
alwaysMarkNsfw: false,
@@ -402,6 +404,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
402404
canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
403405
canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)),
404406
canUseDriveFileInSoundSettings: calc('canUseDriveFileInSoundSettings', vs => vs.some(v => v === true)),
407+
canUseReaction: calc('canUseReaction', vs => vs.some(v => v === true)),
405408
canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
406409
driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
407410
alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),

packages/backend/src/models/json-schema/role.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ export const packedRolePoliciesSchema = {
248248
type: 'boolean',
249249
optional: false, nullable: false,
250250
},
251+
canUseReaction: {
252+
type: 'boolean',
253+
optional: false, nullable: false,
254+
},
251255
canHideAds: {
252256
type: 'boolean',
253257
optional: false, nullable: false,

packages/frontend/src/components/MkNote.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ SPDX-License-Identifier: AGPL-3.0-only
119119
<i class="ti ti-ban"></i>
120120
</button>
121121
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
122-
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
123-
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
124-
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
122+
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
123+
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
124+
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction" class="ti ti-heart"></i>
125125
<i v-else class="ti ti-plus"></i>
126126
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
127127
</button>
@@ -157,6 +157,7 @@ SPDX-License-Identifier: AGPL-3.0-only
157157
import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
158158
import * as mfm from 'mfm-js';
159159
import * as Misskey from 'misskey-js';
160+
import MkButton from './MkButton.vue';
160161
import MkNoteSub from '@/components/MkNoteSub.vue';
161162
import MkNoteHeader from '@/components/MkNoteHeader.vue';
162163
import MkNoteSimple from '@/components/MkNoteSimple.vue';
@@ -165,7 +166,6 @@ import MkReactionsViewerDetails from '@/components/MkReactionsViewer.details.vue
165166
import MkMediaList from '@/components/MkMediaList.vue';
166167
import MkCwButton from '@/components/MkCwButton.vue';
167168
import MkPoll from '@/components/MkPoll.vue';
168-
import MkButton from './MkButton.vue';
169169
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
170170
import MkUrlPreview from '@/components/MkUrlPreview.vue';
171171
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
@@ -380,7 +380,7 @@ function reply(viaKeyboard = false): void {
380380
function react(viaKeyboard = false): void {
381381
pleaseLogin();
382382
showMovedDialog();
383-
if (appearNote.value.reactionAcceptance === 'likeOnly') {
383+
if (appearNote.value.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) {
384384
sound.playMisskeySfx('reaction');
385385
386386
if (props.mock) {

packages/frontend/src/const.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export const ROLE_POLICIES = [
9292
'canSearchNotes',
9393
'canUseTranslator',
9494
'canUseDriveFileInSoundSettings',
95+
'canUseReaction',
9596
'canHideAds',
9697
'driveCapacityMb',
9798
'alwaysMarkNsfw',

packages/frontend/src/pages/admin/roles.editor.vue

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,26 @@ SPDX-License-Identifier: AGPL-3.0-only
503503
</div>
504504
</MkFolder>
505505

506+
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUseReaction, 'canUseReaction'])">
507+
<template #label>{{ i18n.ts._role._options.canUseReaction }}</template>
508+
<template #suffix>
509+
<span v-if="role.policies.canUseReaction.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
510+
<span v-else>{{ role.policies.canUseReaction.value ? i18n.ts.yes : i18n.ts.no }}</span>
511+
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canUseReaction)"></i></span>
512+
</template>
513+
<div class="_gaps">
514+
<MkSwitch v-model="role.policies.canUseReaction.useDefault" :readonly="readonly">
515+
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
516+
</MkSwitch>
517+
<MkSwitch v-model="role.policies.canUseReaction.value" :disabled="role.policies.canUseReaction.useDefault" :readonly="readonly">
518+
<template #label>{{ i18n.ts.enable }}</template>
519+
</MkSwitch>
520+
<MkRange v-model="role.policies.canUseReaction.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 : ''">
521+
<template #label>{{ i18n.ts._role.priority }}</template>
522+
</MkRange>
523+
</div>
524+
</MkFolder>
525+
506526
<MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])">
507527
<template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
508528
<template #suffix>

0 commit comments

Comments
 (0)