diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js index 2dc9653ea..df137157a 100644 --- a/src/components/settings_modal/helpers/setting.js +++ b/src/components/settings_modal/helpers/setting.js @@ -78,7 +78,6 @@ export default { }, computed: { draft: { - // TODO allow passing shared draft object? get () { if (this.realSource === 'admin' || this.path == null) { return get(this.$store.state.adminSettings.draft, this.canonPath) diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index fbace15df..8517e0135 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -1,15 +1,19 @@ -import { filter, trim, debounce } from 'lodash' +import { mapState, mapActions } from 'pinia' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { v4 as uuidv4 } from 'uuid'; + import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' import UnitSetting from '../helpers/unit_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import Select from 'src/components/select/select.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' const FilteringTab = { data () { return { - muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n'), replyVisibilityOptions: ['all', 'following', 'self'].map(mode => ({ key: mode, value: mode, @@ -21,26 +25,95 @@ const FilteringTab = { BooleanSetting, ChoiceSetting, UnitSetting, - IntegerSetting + IntegerSetting, + Checkbox, + Select }, computed: { ...SharedComputedObject(), - muteWordsString: { - get () { - return this.muteWordsStringLocal - }, - set (value) { - this.muteWordsStringLocal = value - this.debouncedSetMuteWords(value) + ...mapState( + useServerSideStorageStore, + { + muteFilters: store => Object.entries(store.prefsStorage.simple.muteFilters), + muteFiltersObject: store => store.prefsStorage.simple.muteFilters } + ) + }, + methods: { + ...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference', 'pushServerSideStorage']), + getDatetimeLocal (timestamp) { + const date = new Date(timestamp) + const datetime = [ + date.getFullYear(), + '-', + date.getMonth() < 9 ? ('0' + (date.getMonth() + 1)) : (date.getMonth() + 1), + '-', + date.getDate() < 10 ? ('0' + date.getDate()) : date.getDate(), + 'T', + date.getHours() < 10 ? ('0' + date.getHours()) : date.getHours(), + ':', + date.getMinutes() < 10 ? ('0' + date.getMinutes()) : date.getMinutes(), + ].join('') + return datetime }, - debouncedSetMuteWords () { - return debounce((value) => { - this.$store.dispatch('setOption', { - name: 'muteWords', - value: filter(value.split('\n'), (word) => trim(word).length > 0) - }) - }, 1000) + checkRegexValid (id) { + const filter = this.muteFiltersObject[id] + if (filter.type !== 'regexp') return true + const { value } = filter + let valid = true + try { + new RegExp(value) + } catch (e) { + valid = false + console.error('Invalid RegExp: ' + value) + } + return valid + }, + createFilter () { + const filter = { + type: 'word', + value: '', + name: 'New Filter', + enabled: true, + expires: null, + hide: false, + order: this.muteFilters.length + 2 + } + const newId = uuidv4() + + this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter }) + this.pushServerSideStorage() + }, + copyFilter (id) { + const filter = { ...this.muteFiltersObject[id] } + const newId = uuidv4() + + this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter }) + this.pushServerSideStorage() + }, + deleteFilter (id) { + this.unsetPreference({ path: 'simple.muteFilters.' + id , value: null }) + this.pushServerSideStorage() + }, + updateFilter(id, field, value) { + const filter = { ...this.muteFiltersObject[id] } + if (field === 'expires-never') { + // filter[field] = value + if (!value) { + const offset = 1000 * 60 * 60 * 24 * 14 // 2 weeks + const date = Date.now() + offset + filter.expires = date + } else { + filter.expires = null + } + } else if (field === 'expires') { + const parsed = Date.parse(value) + filter.expires = parsed.valueOf() + } else { + filter[field] = value + } + this.setPreference({ path: 'simple.muteFilters.' + id , value: filter }) + this.pushServerSideStorage() } }, // Updating nested properties diff --git a/src/components/settings_modal/tabs/filtering_tab.scss b/src/components/settings_modal/tabs/filtering_tab.scss new file mode 100644 index 000000000..ec98e094d --- /dev/null +++ b/src/components/settings_modal/tabs/filtering_tab.scss @@ -0,0 +1,61 @@ +.filtering-tab { + .muteFilterContainer { + border: 1px solid var(--border); + border-radius: var(--roundness); + + height: 20vh; + overflow-y: auto; + } + + .mute-filter { + border: 1px solid var(--border); + border-radius: var(--roundness); + margin: 0.5em; + padding: 0.5em; + + display: grid; + grid-template-columns: fit-content() 1fr fit-content(); + align-items: baseline; + grid-gap: 0.5em; + } + + .filter-name { + grid-column: 1 / span 2; + grid-row: 1; + } + + .alert, .button-default { + display: inline-block; + line-height: 2; + padding: 0 0.5em; + } + + .filter-enabled { + grid-column: 3; + grid-row: 1; + + text-align: right; + } + + .filter-field { + display: grid; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + grid-column: 1 / span 3; + align-items: baseline; + + label { + grid-column: 1; + text-align: right; + } + + .filter-field-value { + grid-column: 2 / span 2; + } + } + + .filter-buttons { + grid-column: 1 / span 3; + justify-self: end; + } +} diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index 32325d423..538ab047c 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -1,5 +1,5 @@