diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index 8517e0135..882f43c69 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -1,3 +1,4 @@ +import { throttle } from 'lodash' import { mapState, mapActions } from 'pinia' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { v4 as uuidv4 } from 'uuid'; @@ -40,7 +41,8 @@ const FilteringTab = { ) }, methods: { - ...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference', 'pushServerSideStorage']), + ...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference']), + pushServerSideStorage: throttle(() => useServerSideStorageStore().pushServerSideStorage(), 500), getDatetimeLocal (timestamp) { const date = new Date(timestamp) const datetime = [ diff --git a/src/components/status/status.js b/src/components/status/status.js index 4f8ba31e4..84155085a 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -14,7 +14,7 @@ import MentionLink from 'src/components/mention_link/mention_link.vue' import StatusActionButtons from 'src/components/status_action_buttons/status_action_buttons.vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' -import { muteWordHits } from '../../services/status_parser/status_parser.js' +import { muteFilterHits } from '../../services/status_parser/status_parser.js' import { unescape, uniqBy } from 'lodash' import { library } from '@fortawesome/fontawesome-svg-core' @@ -161,9 +161,6 @@ const Status = { }, computed: { ...controlledOrUncontrolledGetters(['replying', 'mediaPlaying']), - muteWords () { - return this.mergedConfig.muteWords - }, showReasonMutedThread () { return ( this.status.thread_muted || @@ -221,8 +218,9 @@ const Status = { loggedIn () { return !!this.currentUser }, - muteWordHits () { - return muteWordHits(this.status, this.muteWords) + muteFilterHits () { + console.log(muteFilterHits(this.status)) + return muteFilterHits(this.status) }, botStatus () { return this.status.user.actor_type === 'Service' @@ -256,7 +254,7 @@ const Status = { return [ this.userIsMuted ? 'user' : null, this.status.thread_muted ? 'thread' : null, - (this.muteWordHits.length > 0) ? 'wordfilter' : null, + (this.muteFilterHits.length > 0) ? 'filtered' : null, (this.muteBotStatuses && this.botStatus) ? 'bot' : null, (this.muteSensitiveStatuses && this.sensitiveStatus) ? 'nsfw' : null ].filter(_ => _) @@ -267,14 +265,14 @@ const Status = { switch (this.muteReasons[0]) { case 'user': return this.$t('status.muted_user') case 'thread': return this.$t('status.thread_muted') - case 'wordfilter': + case 'filtered': return this.$t( - 'status.muted_words', + 'status.muted_filters', { - word: this.muteWordHits[0], - numWordsMore: this.muteWordHits.length - 1 + name: this.muteFilterHits[0].name, + filterMore: this.muteFilterHits.length - 1 }, - this.muteWordHits.length + this.muteFilterHits.length ) case 'bot': return this.$t('status.bot_muted') case 'nsfw': return this.$t('status.sensitive_muted') @@ -326,7 +324,7 @@ const Status = { // Don't mute statuses in muted conversation when said conversation is opened (this.inConversation && status.thread_muted) // No excuses if post has muted words - ) && !this.muteWordHits.length > 0 + ) && !this.muteFilterHits.length > 0 }, hideMutedUsers () { return this.mergedConfig.hideMutedPosts @@ -345,7 +343,8 @@ const Status = { (this.muted && this.hideFilteredStatuses) || (this.userIsMuted && this.hideMutedUsers) || (this.status.thread_muted && this.hideMutedThreads) || - (this.muteWordHits.length > 0 && this.hideWordFilteredPosts) + (this.muteFilterHits.length > 0 && this.hideWordFilteredPosts) || + (this.muteFilterHits[0]?.hide) ) }, isFocused () { diff --git a/src/i18n/en.json b/src/i18n/en.json index 3c6e823cb..18039eb1b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1281,6 +1281,7 @@ "copy_link": "Copy link to status", "external_source": "External source", "muted_words": "Wordfiltered: {word} | Wordfiltered: {word} and {numWordsMore} more words", + "muted_filters": "Filtered: {name} | Wordfiltered: {name} and {filtersMore} more words", "multi_reason_mute": "{main} + one more reason | {main} + {numReasonsMore} more reasons", "muted_user": "User muted", "thread_muted": "Thread muted", diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 4889aeb78..74fd91204 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -1,4 +1,4 @@ -import { muteWordHits } from '../status_parser/status_parser.js' +import { muteFilterHits } from '../status_parser/status_parser.js' import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js' import { useI18nStore } from 'src/stores/i18n.js' import { useAnnouncementsStore } from 'src/stores/announcements' @@ -58,10 +58,10 @@ const sortById = (a, b) => { } } -const isMutedNotification = (store, notification) => { - if (!notification.status) return - const rootGetters = store.rootGetters || store.getters - return notification.status.muted || muteWordHits(notification.status, rootGetters.mergedConfig.muteWords).length > 0 +const isMutedNotification = (notification) => { + if (!notification.status) return false + if (notification.status.muted) return true + return muteFilterHits(notification.status).length > 0 } export const maybeShowNotification = (store, notification) => { @@ -69,7 +69,7 @@ export const maybeShowNotification = (store, notification) => { if (notification.seen) return if (!visibleTypes(store).includes(notification.type)) return - if (notification.type === 'mention' && isMutedNotification(store, notification)) return + if (notification.type === 'mention' && isMutedNotification(notification)) return const notificationObject = prepareNotificationObject(notification, useI18nStore().i18n) showDesktopNotification(rootState, notificationObject) diff --git a/src/services/status_parser/status_parser.js b/src/services/status_parser/status_parser.js index ed0f6d572..90fbebaef 100644 --- a/src/services/status_parser/status_parser.js +++ b/src/services/status_parser/status_parser.js @@ -1,11 +1,30 @@ -import { filter } from 'lodash' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' -export const muteWordHits = (status, muteWords) => { +export const muteFilterHits = (status) => { const statusText = status.text.toLowerCase() const statusSummary = status.summary.toLowerCase() - const hits = filter(muteWords, (muteWord) => { - return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase()) - }) - return hits + const muteFilters = Object.values(useServerSideStorageStore().prefsStorage.simple.muteFilters) + + return muteFilters.toSorted((a,b) => b.order - a.order).map(filter => { + const { hide, expires, name, value, type} = filter + if (expires !== null && expires < Date.now()) return false + switch (type) { + case 'word': { + if (statusText.includes(value) || statusSummary.includes(value)) { + return { hide, name } + } + } + case 'regexp': { + try { + const re = new RegExp(value, 'i') + if (re.test(statusText) || re.test(statusSummary)) { + return { hide, name } + } + } catch { + return false + } + } + } + }).filter(_ => _) }