diff --git a/changelog.d/mutes-sync.add b/changelog.d/mutes-sync.add new file mode 100644 index 000000000..e8e0e462a --- /dev/null +++ b/changelog.d/mutes-sync.add @@ -0,0 +1 @@ +Synchronized mutes, advanced mute control (regexp, expiry, naming) diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index bbd1e8631..4b404023d 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -1,4 +1,4 @@ -import { throttle } from 'lodash' +import { throttle, cloneDeep } from 'lodash' import { mapState, mapActions } from 'pinia' import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { v4 as uuidv4 } from 'uuid'; @@ -14,12 +14,19 @@ import SharedComputedObject from '../helpers/shared_computed_object.js' const FilteringTab = { data () { + console.log(cloneDeep(useServerSideStorageStore().prefsStorage.simple.muteFilters)) return { replyVisibilityOptions: ['all', 'following', 'self'].map(mode => ({ key: mode, value: mode, label: this.$t(`settings.reply_visibility_${mode}`) - })) + })), + muteFiltersDraftObject: cloneDeep(useServerSideStorageStore().prefsStorage.simple.muteFilters), + muteFiltersDraftDirty: Object.fromEntries( + Object.entries( + useServerSideStorageStore().prefsStorage.simple.muteFilters + ).map(([k]) => [k, false]) + ) } }, components: { @@ -38,12 +45,13 @@ const FilteringTab = { muteFilters: store => Object.entries(store.prefsStorage.simple.muteFilters), muteFiltersObject: store => store.prefsStorage.simple.muteFilters } - ) + ), + muteFiltersDraft () { + return Object.entries(this.muteFiltersDraftObject) + } }, methods: { - ...mapActions(useServerSideStorageStore, ['unsetPreference']), - pushServerSideStorage: throttle(() => useServerSideStorageStore().pushServerSideStorage(), 500), - setPreference: throttle(x => useServerSideStorageStore().setPreference(x), 500), + ...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference', 'pushServerSideStorage']), getDatetimeLocal (timestamp) { const date = new Date(timestamp) const datetime = [ @@ -84,22 +92,25 @@ const FilteringTab = { } const newId = uuidv4() + this.muteFiltersDraftObject[newId] = filter this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter }) this.pushServerSideStorage() }, copyFilter (id) { - const filter = { ...this.muteFiltersObject[id] } + const filter = { ...this.muteFiltersDraftObject[id] } const newId = uuidv4() + this.muteFiltersDraftObject[newId] = filter this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter }) this.pushServerSideStorage() }, deleteFilter (id) { + delete this.muteFiltersDraftObject[id] this.unsetPreference({ path: 'simple.muteFilters.' + id , value: null }) this.pushServerSideStorage() }, updateFilter(id, field, value) { - const filter = { ...this.muteFiltersObject[id] } + const filter = { ...this.muteFiltersDraftObject[id] } if (field === 'expires-never') { // filter[field] = value if (!value) { @@ -115,8 +126,15 @@ const FilteringTab = { } else { filter[field] = value } - this.setPreference({ path: 'simple.muteFilters.' + id , value: filter }) + this.muteFiltersDraftObject[id] = filter + this.muteFiltersDraftDirty[id] = true + console.log(this.muteFiltersDraftDirty) + }, + saveFilter(id) { + this.setPreference({ path: 'simple.muteFilters.' + id , value: this.muteFiltersDraftObject[id] }) this.pushServerSideStorage() + this.muteFiltersDraftDirty[id] = false + console.log(this.muteFiltersDraftDirty) } }, // Updating nested properties diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index d3c18a184..be4ac3fcf 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -76,7 +76,7 @@
@@ -201,6 +201,16 @@ > {{ $t('settings.filter.delete') }} + {{ ' ' }} +
diff --git a/src/i18n/en.json b/src/i18n/en.json index 4e62a365c..da2d79a69 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -422,6 +422,7 @@ "expires": "Expires", "expired": "Expired", "copy": "Copy filter", + "save": "Save filter", "delete": "Remove filter", "new": "Create filter", "regexp_error": "Invalid Regular Expression", diff --git a/src/modules/config_declaration.js b/src/modules/config_declaration.js new file mode 100644 index 000000000..05fb250e6 --- /dev/null +++ b/src/modules/config_declaration.js @@ -0,0 +1,41 @@ +export const CONFIG_MIGRATION = 1 +import { v4 as uuidv4 } from 'uuid'; + +// for future use +/* +const simpleDeclaration = { + store: 'server-side', + migration(serverside, rootState) { + serverside.setPreference({ path: 'simple.' + field, value: rootState.config[oldField ?? field] }) + } +} +*/ + +export const declarations = [ + { + field: 'muteFilters', + store: 'server-side', + migrationFlag: 'configMigration', + migrationNum: 1, + description: 'Mute filters, wordfilter/regexp/etc', + valueType: 'complex', + migration (serverside, rootState) { + rootState.config.muteWords.forEach((word, order) => { + const uniqueId = uuidv4() + + serverside.setPreference({ + path: 'simple.muteFilters.' + uniqueId, + value: { + type: 'word', + value: word, + name: word, + enabled: true, + expires: null, + hide: false, + order + } + }) + }) + } + } +] diff --git a/src/modules/users.js b/src/modules/users.js index d9dae91ea..d5ef6cdf5 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,5 +1,4 @@ import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash' -import { v4 as uuidv4 } from 'uuid'; import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import { windowWidth, windowHeight } from '../services/window_utils/window_utils' @@ -9,7 +8,9 @@ import { registerPushNotifications, unregisterPushNotifications } from '../servi import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { useServerSideStorageStore, CONFIG_MIGRATION } from 'src/stores/serverSideStorage' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' + +import { declarations } from 'src/modules/config_declaration' // TODO: Unify with mergeOrAdd in statuses.js export const mergeOrAdd = (arr, obj, item) => { @@ -637,33 +638,17 @@ const users = { /**/ const { configMigration } = useServerSideStorageStore().flagStorage - - // Wordfilter migration - if (configMigration < 1) { - // Convert existing wordfilter into synced one - store.rootState.config.muteWords.forEach((word, order) => { - const uniqueId = uuidv4() - - useServerSideStorageStore().setPreference({ - path: 'simple.muteFilters.' + uniqueId, - value: { - type: 'word', - value: word, - name: word, - enabled: true, - expires: null, - hide: false, - order - } - }) + declarations + .filter(x => { + return x.store === 'server-side' && + (x.migrationNum ?? x.migrationNum > configMigration) + }) + .toSorted((a, b) => a.configMigration - b.configMigration) + .forEach(value => { + value.migration(useServerSideStorageStore(), store.rootState) + useServerSideStorageStore().setFlag({ flag: 'configMigration', value: value.migrationNum }) + useServerSideStorageStore().pushServerSideStorage() }) - } - - if (configMigration < CONFIG_MIGRATION) { - // Update the flag - useServerSideStorageStore().setFlag({ flag: 'configMigration', value: CONFIG_MIGRATION }) - useServerSideStorageStore().pushServerSideStorage() - } if (user.token) { dispatch('setWsToken', user.token) diff --git a/src/services/status_parser/status_parser.js b/src/services/status_parser/status_parser.js index 96624fc5e..64be68d78 100644 --- a/src/services/status_parser/status_parser.js +++ b/src/services/status_parser/status_parser.js @@ -5,6 +5,7 @@ export const muteFilterHits = (muteFilters, status) => { return muteFilters.toSorted((a,b) => b.order - a.order).map(filter => { const { hide, expires, name, value, type, enabled} = filter if (!enabled) return false + if (value === '') return false if (expires !== null && expires < Date.now()) return false switch (type) { case 'word': {