add emoji reaction filtering

This commit is contained in:
notfire 2025-03-30 14:33:29 -04:00
parent c43ddf6c7b
commit 7c275adfc8
Signed by: notfire
GPG key ID: 3AFDACAAB4E56B16
10 changed files with 99 additions and 8 deletions

View file

@ -19,6 +19,9 @@
- polls can now have up to 20 options instead of 4 - polls can now have up to 20 options instead of 4
- polls can now last anywhere from 1 second to 1000 years - polls can now last anywhere from 1 second to 1000 years
- optional alternate gravestone when using websockets that still shows post content but removes buttons - optional alternate gravestone when using websockets that still shows post content but removes buttons
- filters for reactions:
- matching by regex, domain-specific, or catch-all
- filters the actual reactions to post as well as notifications
--- ---

View file

@ -2,6 +2,8 @@ import UserAvatar from '../user_avatar/user_avatar.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue' import UserListPopover from '../user_list_popover/user_list_popover.vue'
import StillImage from '../still-image/still-image.vue' import StillImage from '../still-image/still-image.vue'
import { toRaw } from 'vue'
const EMOJI_REACTION_COUNT_CUTOFF = 12 const EMOJI_REACTION_COUNT_CUTOFF = 12
const findEmojiByReplacement = (state, replacement) => { const findEmojiByReplacement = (state, replacement) => {
@ -24,11 +26,6 @@ const EmojiReactions = {
tooManyReactions () { tooManyReactions () {
return this.status.emoji_reactions.length > EMOJI_REACTION_COUNT_CUTOFF return this.status.emoji_reactions.length > EMOJI_REACTION_COUNT_CUTOFF
}, },
emojiReactions () {
return this.showAll
? this.status.emoji_reactions
: this.status.emoji_reactions.slice(0, EMOJI_REACTION_COUNT_CUTOFF)
},
showMoreString () { showMoreString () {
return `+${this.status.emoji_reactions.length - EMOJI_REACTION_COUNT_CUTOFF}` return `+${this.status.emoji_reactions.length - EMOJI_REACTION_COUNT_CUTOFF}`
}, },
@ -44,9 +41,50 @@ const EmojiReactions = {
}, },
loggedIn () { loggedIn () {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
},
filteredReactions () {
// this is fucking Jank.
var finalReacts = this.emojiReactions()
var tempList = []
var reactionsFilterItems = this.$store.getters.mergedConfig.reactionsFilterItems.split("\n")
if (reactionsFilterItems.length > 0 && finalReacts.length > 0) {
reactionsFilterItems.forEach((item) => {
var toPush = []
finalReacts.forEach((react) => {
var rawReact = toRaw(react)["name"]
if (
// match regex
// >1 check is to prevent = just disabling all emoji reacts
item.charAt(0) == "=" && item.length > 1 && rawReact.match(item.substring(1))
// match everything explicit, if there's an @
|| rawReact == item.match(/.*@.*/) ? item : null
// match everything else
|| (rawReact.match(/.*@.*/) ? rawReact.split("@")[0] : rawReact) == item
) {
toPush.push(react)
}
})
toPush.forEach((nice) => {
tempList.push(nice)
})
})
}
tempList.forEach((item) => {
finalReacts.splice(finalReacts.indexOf(item), 1)
})
return finalReacts
} }
}, },
methods: { methods: {
emojiReactions () {
return this.showAll
? this.status.emoji_reactions
: this.status.emoji_reactions.slice(0, EMOJI_REACTION_COUNT_CUTOFF)
},
toggleShowAll () { toggleShowAll () {
this.showAll = !this.showAll this.showAll = !this.showAll
}, },

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="emoji-reactions"> <div class="emoji-reactions">
<UserListPopover <UserListPopover
v-for="(reaction) in emojiReactions" v-for="(reaction) in filteredReactions"
:key="reaction.url || reaction.name" :key="reaction.url || reaction.name"
:users="accountsForEmoji[reaction.url || reaction.name]" :users="accountsForEmoji[reaction.url || reaction.name]"
> >

View file

@ -112,6 +112,33 @@ const Notification = {
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
}) })
this.hideDenyConfirmDialog() this.hideDenyConfirmDialog()
},
filtered (emoji) {
var isFiltered = false
if (emoji.match(/.*:.*/)) {
emoji = emoji.replaceAll(":", "")
}
var reactionsFilterItems = this.$store.getters.mergedConfig.reactionsFilterItems.split("\n")
if (reactionsFilterItems.length > 0)
reactionsFilterItems.forEach((item) => {
console.log(emoji)
if (
// match regex
// >1 check is to prevent = just disabling all emoji reacts
item.charAt(0) == "=" && item.length > 1 && emoji.match(item.substring(1))
// match everything explicit, if there's an @
|| emoji == item.match(/.*@.*/) ? item : null
// match everything else
|| (emoji.match(/.*@.*/) ? emoji.split("@")[0] : emoji) == item
) {
isFiltered = true
}
})
return isFiltered
} }
}, },
computed: { computed: {

View file

@ -5,7 +5,7 @@
:compact="true" :compact="true"
:statusoid="notification.status" :statusoid="notification.status"
/> />
<div v-else> <div v-else-if="(notification.emoji && !filtered(notification.emoji)) || !notification.emoji">
<div <div
v-if="needMute && !unmuted" v-if="needMute && !unmuted"
class="Notification container -muted" class="Notification container -muted"

View file

@ -2,6 +2,7 @@ import { filter, trim, debounce } from 'lodash'
import BooleanSetting from '../helpers/boolean_setting.vue' import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue'
import IntegerSetting from '../helpers/integer_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue'
import TextSetting from '../helpers/text_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
@ -19,7 +20,8 @@ const FilteringTab = {
components: { components: {
BooleanSetting, BooleanSetting,
ChoiceSetting, ChoiceSetting,
IntegerSetting IntegerSetting,
TextSetting
}, },
computed: { computed: {
...SharedComputedObject(), ...SharedComputedObject(),

View file

@ -115,6 +115,23 @@
</li> </li>
</ul> </ul>
</div> </div>
<div
class="setting-item"
>
<h2>{{ $t('settings.notification_setting_reactions') }}</h2>
<ul class="setting-list">
<li>
<h3>{{ $t('settings.reactions_filter') }}</h3>
<TextSetting
id="reactionsFilterItems"
path="reactionsFilterItems"
>
</TextSetting>
<!-- this is fucking stupid but for some reason the internationalization stuff (Not calling it i18n) hates this, so taking the liberty to also add styling -->
<div>Put reactions to remove in this box (formatted like reaction, no colons), one line for each. Results get matched like:<br><b><code>reaction</code></b> is a catch-all for all domains<br><b><code>reaction@domain.tld</code></b> is a reaction for a specific domain<br><b><code>=reaction</code></b> is a match via regex</div>
</li>
</ul>
</div>
</div> </div>
</template> </template>
<script src="./filtering_tab.js"></script> <script src="./filtering_tab.js"></script>

View file

@ -686,6 +686,7 @@
"notification_setting_hide_if_cw": "Hide the contents of push notifications if under a Content Warning", "notification_setting_hide_if_cw": "Hide the contents of push notifications if under a Content Warning",
"notification_setting_hide_notification_contents": "Hide the sender and contents of push notifications", "notification_setting_hide_notification_contents": "Hide the sender and contents of push notifications",
"notification_setting_privacy": "Privacy", "notification_setting_privacy": "Privacy",
"notification_setting_reactions": "Reactions",
"notification_setting_sounds": "Sounds", "notification_setting_sounds": "Sounds",
"notification_visibility": "Types of notifications to show", "notification_visibility": "Types of notifications to show",
"notification_visibility_bites": "Bites", "notification_visibility_bites": "Bites",
@ -719,6 +720,7 @@
}, },
"profile_tab": "Profile", "profile_tab": "Profile",
"radii_help": "Set up interface edge rounding (in pixels)", "radii_help": "Set up interface edge rounding (in pixels)",
"reactions_filter": "Filter reactions by name",
"recurse_search": "When searching, run searches until no results are found anymore", "recurse_search": "When searching, run searches until no results are found anymore",
"recurse_search_limit": "Number of posts to stop recurse searching at (>300 will cause massive performance issues)", "recurse_search_limit": "Number of posts to stop recurse searching at (>300 will cause massive performance issues)",
"refresh_token": "Refresh token", "refresh_token": "Refresh token",

View file

@ -124,6 +124,7 @@ export const defaultState = {
boostsFollowDefVis: false, boostsFollowDefVis: false,
searchPaginationLimit: 40, searchPaginationLimit: 40,
sillyFeatures: false, sillyFeatures: false,
reactionsFilterItems: '',
recurseSearch: false, recurseSearch: false,
recurseSearchLimit: 100, recurseSearchLimit: 100,
renderMisskeyMarkdown: undefined, renderMisskeyMarkdown: undefined,

View file

@ -80,6 +80,7 @@ const defaultState = {
deletePostsStreaming: true, deletePostsStreaming: true,
searchPaginationLimit: 40, searchPaginationLimit: 40,
sillyFeatures: false, sillyFeatures: false,
reactionsFilterItems: '',
recurseSearch: false, recurseSearch: false,
recurseSearchLimit: 100, recurseSearchLimit: 100,
renderMisskeyMarkdown: true, renderMisskeyMarkdown: true,