config: add infrastructure to cache parsed config values

Using muteWords as an example.
Currently this doesn’t help much
but the subsequent commit will extend muteWord capabilities
making parsing more costly
This commit is contained in:
Oneric 2025-10-26 00:00:00 +00:00
commit f2c55423fd
4 changed files with 61 additions and 19 deletions

View file

@ -169,8 +169,8 @@ const Status = {
},
computed: {
...controlledOrUncontrolledGetters(['replying', 'quoting', 'mediaPlaying']),
muteWords () {
return this.mergedConfig.muteWords
muteWordRules () {
return this.$store.getters.parsedConfigVal('muteWords')
},
showReasonMutedThread () {
return (
@ -230,7 +230,7 @@ const Status = {
return !!this.currentUser
},
muteWordHits () {
return muteWordHits(this.status, this.muteWords)
return muteWordHits(this.status, this.muteWordRules)
},
rtBotStatus () {
return this.statusoid.user.bot

View file

@ -144,9 +144,28 @@ function updateLocalSettings(store, settingEntries, version = null) {
store.commit('setOption', { name: 'profileVersion', value: version })
}
/**
* Parses the raw mute-word string into an array of mute rules
* containing the original filter defintion and a predicator function
* and optionally (for optimisation purposes when checking against many mute rules)
* the lower-cased version of the text as arguments.
*
* muteRule = { name: string, predicate: (string, string) => boolean }
*/
const parseMuteWords = (rawList) => {
return rawList.map((word) => {
const muteWord = word.toLowerCase()
let predicate = (text, textLowCased) => (textLowCased || text?.toLowerCase())?.includes(muteWord)
return { name: word, predicate: predicate }
})
}
const settingParsers = {
muteWords: parseMuteWords,
}
const config = {
state: { ...defaultState },
state: { ...defaultState, '__parsed_cache': {} },
getters: {
defaultConfig (state, getters, rootState, rootGetters) {
const { instance } = rootState
@ -161,14 +180,32 @@ const config = {
const { defaultConfig } = rootGetters
return {
...defaultConfig,
// Do not override with undefined
...Object.fromEntries(Object.entries(state).filter(([k, v]) => v !== undefined))
// Do not override with undefined and exclude private caches
...Object.fromEntries(Object.entries(state).filter(([k, v]) => v !== undefined && k !== '__parsed_cache'))
}
},
parsedConfigVal (state, getters, rootState, rootGetters) {
return (key) => {
let cached = state['__parsed_cache'][key]
if (cached !== undefined) return cached;
const parser = settingParsers[key]
if (!parser) return undefined;
// we can't use mergedConfig yet here
const rawVal = state[key] || rootState.instance?.[key] || defaultState[key];
const parsed = parser(rawVal);
state['__parsed_cache'][key] = parsed
return parsed
}
}
},
mutations: {
setOption (state, { name, value }) {
state[name] = value
if (settingParsers.hasOwnProperty(name)) {
state['__parsed_cache'][name] = undefined
}
},
setHighlight (state, { user, color, type }) {
const data = this.state.config.highlight[user]
@ -189,8 +226,9 @@ const config = {
timeout: 5000
}
store.dispatch('pushGlobalNotice', notice)
let {'__parsed_cache': _, ...currentSettings} = store.state
store.rootState.api.backendInteractor.saveSettingsProfile({
settings: store.state, profileName: store.state.profile, version: store.state.profileVersion
settings: currentSettings, profileName: store.state.profile, version: store.state.profileVersion
}).then(() => {
store.dispatch('removeGlobalNotice', notice)
store.dispatch('pushGlobalNotice', {

View file

@ -48,7 +48,7 @@ const sortById = (a, b) => {
const isMutedNotification = (store, notification) => {
if (!notification.status) return
return notification.status.muted || muteWordHits(notification.status, store.rootGetters.mergedConfig.muteWords).length > 0
return notification.status.muted || muteWordHits(notification.status, store.rootGetters.parsedConfigVal('muteWords')).length > 0
}
export const maybeShowNotification = (store, notification) => {

View file

@ -1,18 +1,22 @@
import { filter } from 'lodash'
export const muteWordHits = (status, muteWords) => {
const statusText = status.text.toLowerCase()
const statusSummary = status.summary.toLowerCase()
export const muteWordHits = (status, muteRules) => {
const statusTextLC = status.text.toLowerCase()
const statusSummaryLC = status.summary.toLowerCase()
const altTextPairs = status.attachments.map((a) => [a.description, a.description?.toLowerCase()])
const hits = filter(muteWords, (muteWord) => {
muteWord = muteWord.toLowerCase()
const hits = []
return (
statusText.includes(muteWord) ||
statusSummary.includes(muteWord) ||
status.attachments.some((a) => a.description?.toLowerCase().includes(muteWord))
)
})
for (const rule of muteRules) {
const pred = rule.predicate
if (pred(status.text, statusTextLC) ||
pred(status.summary, statusSummaryLC) ||
altTextPairs.some(([t, tlc]) => pred(t, tlc))
) {
hits.push(rule.name)
}
}
return hits
}