diff --git a/src/config/locales/en/translation.json b/src/config/locales/en/translation.json index 0f69a0e8..fab5f6bf 100644 --- a/src/config/locales/en/translation.json +++ b/src/config/locales/en/translation.json @@ -114,6 +114,39 @@ "local": "Local Timeline", "public": "Public Timeline" } + }, + "filters": { + "title": "Filters", + "form": { + "phrase": "Keyword or phrase", + "expire": "Expire after", + "context": "Filter contexts", + "irreversible": "Drop instead of hide", + "whole_word": "Whole word", + "submit": "Submit", + "cancel": "Cancel" + }, + "expires": { + "never": "Never", + "30_minutes": "30 minutes", + "1_hour": "1 hour", + "6_hours": "6 hours", + "12_hours": "12 hours", + "1_day": "1 day", + "1_week": "1 week" + }, + "new": { + "title": "New" + }, + "edit": { + "title": "Edit" + }, + "delete": { + "title": "Delete", + "confirm": "Are you sure to delete this filter?", + "confirm_ok": "Delete", + "confirm_cancel": "Cancel" + } } }, "preferences": { @@ -452,7 +485,8 @@ "domain_confirmed": "{{domain}} is confirmed, please login", "domain_doesnt_exist": "Failed to connect {{domain}}, make sure the server URL", "loading": "Loading...", - "language_not_support_spellchecker_error": "This language is not supported by Spellchecker" + "language_not_support_spellchecker_error": "This language is not supported by Spellchecker", + "update_filter_error": "Failed to update the filter" }, "validation": { "login": { diff --git a/src/renderer/components/Settings.vue b/src/renderer/components/Settings.vue index 5ade0bd0..2a1126ad 100644 --- a/src/renderer/components/Settings.vue +++ b/src/renderer/components/Settings.vue @@ -28,6 +28,10 @@ {{ $t('settings.timeline.title') }} + + + {{ $t('settings.filters.title') }} + diff --git a/src/renderer/components/Settings/Filters.vue b/src/renderer/components/Settings/Filters.vue new file mode 100644 index 00000000..73f0c964 --- /dev/null +++ b/src/renderer/components/Settings/Filters.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/src/renderer/components/Settings/Filters/Edit.vue b/src/renderer/components/Settings/Filters/Edit.vue new file mode 100644 index 00000000..06efccd2 --- /dev/null +++ b/src/renderer/components/Settings/Filters/Edit.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/renderer/components/Settings/Filters/New.vue b/src/renderer/components/Settings/Filters/New.vue new file mode 100644 index 00000000..0cb481f1 --- /dev/null +++ b/src/renderer/components/Settings/Filters/New.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/renderer/components/Settings/Filters/form.vue b/src/renderer/components/Settings/Filters/form.vue new file mode 100644 index 00000000..35ec0c11 --- /dev/null +++ b/src/renderer/components/Settings/Filters/form.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/renderer/router/index.ts b/src/renderer/router/index.ts index 0b15b505..6c0a893b 100644 --- a/src/renderer/router/index.ts +++ b/src/renderer/router/index.ts @@ -14,6 +14,9 @@ import GlobalHeader from '@/components/GlobalHeader.vue' import Settings from '@/components/Settings.vue' import SettingsGeneral from '@/components/Settings/General.vue' import SettingsTimeline from '@/components/Settings/Timeline.vue' +import SettingsFilters from '@/components/Settings/Filters.vue' +import SettingsFiltersEdit from '@/components/Settings/Filters/Edit.vue' +import SettingsFiltersNew from '@/components/Settings/Filters/New.vue' import TimelineSpace from '@/components/TimelineSpace.vue' import TimelineSpaceContentsHome from '@/components/TimelineSpace/Contents/Home.vue' import TimelineSpaceContentsNotifications from '@/components/TimelineSpace/Contents/Notifications.vue' @@ -100,6 +103,19 @@ const router = new Router({ { path: 'timeline', component: SettingsTimeline + }, + { + path: 'filters', + component: SettingsFilters + }, + { + path: 'filters/new', + component: SettingsFiltersNew + }, + { + path: 'filters/:filter_id/edit', + component: SettingsFiltersEdit, + props: true } ] }, diff --git a/src/renderer/store/Settings.ts b/src/renderer/store/Settings.ts index a9015a1b..c0e28810 100644 --- a/src/renderer/store/Settings.ts +++ b/src/renderer/store/Settings.ts @@ -1,5 +1,6 @@ import General, { GeneralState } from './Settings/General' import Timeline, { TimelineState } from './Settings/Timeline' +import Filters, { FiltersModuleState } from './Settings/Filters' import { Module, MutationTree } from 'vuex' import { RootState } from '@/store' @@ -24,6 +25,7 @@ const mutations: MutationTree = { type SettingsModule = { General: GeneralState Timeline: TimelineState + Filter: FiltersModuleState } export type SettingsModuleState = SettingsModule & SettingsState @@ -32,7 +34,8 @@ const Settings: Module = { namespaced: true, modules: { General, - Timeline + Timeline, + Filters }, state: state, mutations: mutations diff --git a/src/renderer/store/Settings/Filters.ts b/src/renderer/store/Settings/Filters.ts new file mode 100644 index 00000000..2808783d --- /dev/null +++ b/src/renderer/store/Settings/Filters.ts @@ -0,0 +1,82 @@ +import { Module, MutationTree, ActionTree } from 'vuex' +import generator, { Entity } from 'megalodon' +import { RootState } from '@/store' +import EditFilters, { EditFiltersState } from './Filters/Edit' +import NewFilters, { NewFiltersState } from './Filters/New' + +export type FiltersState = { + filters: Array + filtersLoading: boolean +} + +const state = (): FiltersState => ({ + filters: [], + filtersLoading: false +}) + +export const MUTATION_TYPES = { + UPDATE_FILTERS: 'updateFilters', + CHANGE_LOADING: 'changeLoading' +} + +export const mutations: MutationTree = { + [MUTATION_TYPES.UPDATE_FILTERS]: (state, filters: Array) => { + state.filters = filters + }, + [MUTATION_TYPES.CHANGE_LOADING]: (state, loading: boolean) => { + state.filtersLoading = loading + } +} +export const actions: ActionTree = { + fetchFilters: async ({ commit, rootState }): Promise> => { + const client = generator( + rootState.TimelineSpace.sns, + rootState.TimelineSpace.account.baseURL, + rootState.TimelineSpace.account.accessToken, + rootState.App.userAgent + ) + try { + commit(MUTATION_TYPES.CHANGE_LOADING, true) + const res = await client.getFilters() + commit(MUTATION_TYPES.UPDATE_FILTERS, res.data) + return res.data + } finally { + commit(MUTATION_TYPES.CHANGE_LOADING, false) + } + }, + deleteFilter: async ({ commit, dispatch, rootState }, id: string) => { + const client = generator( + rootState.TimelineSpace.sns, + rootState.TimelineSpace.account.baseURL, + rootState.TimelineSpace.account.accessToken, + rootState.App.userAgent + ) + try { + commit(MUTATION_TYPES.CHANGE_LOADING, true) + await client.deleteFilter(id) + await dispatch('fetchFilters') + } finally { + commit(MUTATION_TYPES.CHANGE_LOADING, false) + } + } +} + +type FiltersModule = { + Edit: EditFiltersState + New: NewFiltersState +} + +export type FiltersModuleState = FiltersModule & FiltersState + +const Filters: Module = { + namespaced: true, + state: state, + mutations: mutations, + actions: actions, + modules: { + Edit: EditFilters, + New: NewFilters + } +} + +export default Filters diff --git a/src/renderer/store/Settings/Filters/Edit.ts b/src/renderer/store/Settings/Filters/Edit.ts new file mode 100644 index 00000000..4b743a57 --- /dev/null +++ b/src/renderer/store/Settings/Filters/Edit.ts @@ -0,0 +1,93 @@ +import generator, { Entity } from 'megalodon' +import { Module, MutationTree, ActionTree } from 'vuex' +import { RootState } from '@/store' + +export type EditFiltersState = { + filter: Entity.Filter + loading: boolean +} + +const state = (): EditFiltersState => ({ + filter: { + id: '', + phrase: '', + expires_at: null, + context: [], + irreversible: false, + whole_word: true + } as Entity.Filter, + loading: false +}) + +export const MUTATION_TYPES = { + UPDATE_FILTER: 'updateFilter', + CHANGE_LOADING: 'changeLoading' +} + +export const mutations: MutationTree = { + [MUTATION_TYPES.UPDATE_FILTER]: (state, filter: Entity.Filter) => { + state.filter = filter + }, + [MUTATION_TYPES.CHANGE_LOADING]: (state, loading: boolean) => { + state.loading = loading + } +} + +export const actions: ActionTree = { + fetchFilter: async ({ commit, rootState }, id: string): Promise => { + const client = generator( + rootState.TimelineSpace.sns, + rootState.TimelineSpace.account.baseURL, + rootState.TimelineSpace.account.accessToken, + rootState.App.userAgent + ) + try { + commit(MUTATION_TYPES.CHANGE_LOADING, true) + const res = await client.getFilter(id) + commit(MUTATION_TYPES.UPDATE_FILTER, res.data) + return res.data + } finally { + commit(MUTATION_TYPES.CHANGE_LOADING, false) + } + }, + editFilter: ({ commit, state }, filter: any) => { + const newFilter = Object.assign({}, state.filter, filter) + commit(MUTATION_TYPES.UPDATE_FILTER, newFilter) + }, + updateFilter: async ({ commit, state, rootState }): Promise => { + if (state.filter === null) { + throw new Error('filter is not set') + } + const client = generator( + rootState.TimelineSpace.sns, + rootState.TimelineSpace.account.baseURL, + rootState.TimelineSpace.account.accessToken, + rootState.App.userAgent + ) + try { + commit(MUTATION_TYPES.CHANGE_LOADING, true) + let options = { + irreversible: state.filter.irreversible, + whole_word: state.filter.whole_word + } + if (state.filter.expires_at !== null) { + options = Object.assign({}, options, { + expires_in: state.filter.expires_at + }) + } + const res = await client.updateFilter(state.filter.id, state.filter.phrase, state.filter.context, options) + return res.data + } finally { + commit(MUTATION_TYPES.CHANGE_LOADING, false) + } + } +} + +const EditFilters: Module = { + namespaced: true, + state: state, + mutations: mutations, + actions: actions +} + +export default EditFilters diff --git a/src/renderer/store/Settings/Filters/New.ts b/src/renderer/store/Settings/Filters/New.ts new file mode 100644 index 00000000..896af2a4 --- /dev/null +++ b/src/renderer/store/Settings/Filters/New.ts @@ -0,0 +1,83 @@ +import generator, { Entity } from 'megalodon' +import { Module, MutationTree, ActionTree } from 'vuex' +import { RootState } from '@/store' + +export type NewFiltersState = { + filter: Entity.Filter + loading: boolean +} + +const defaultFilter: Entity.Filter = { + id: '', + phrase: '', + expires_at: null, + context: [], + irreversible: false, + whole_word: true +} + +const state = (): NewFiltersState => ({ + filter: defaultFilter, + loading: false +}) + +export const MUTATION_TYPES = { + UPDATE_FILTER: 'updateFilter', + CHANGE_LOADING: 'changeLoading' +} + +export const mutations: MutationTree = { + [MUTATION_TYPES.UPDATE_FILTER]: (state, filter: Entity.Filter) => { + state.filter = filter + }, + [MUTATION_TYPES.CHANGE_LOADING]: (state, loading: boolean) => { + state.loading = loading + } +} + +export const actions: ActionTree = { + editFilter: ({ commit, state }, filter: any) => { + const newFilter = Object.assign({}, state.filter, filter) + commit(MUTATION_TYPES.UPDATE_FILTER, newFilter) + }, + resetFilter: ({ commit }) => { + commit(MUTATION_TYPES.UPDATE_FILTER, defaultFilter) + }, + createFilter: async ({ commit, state, dispatch, rootState }): Promise => { + if (state.filter === null) { + throw new Error('filter is not set') + } + const client = generator( + rootState.TimelineSpace.sns, + rootState.TimelineSpace.account.baseURL, + rootState.TimelineSpace.account.accessToken, + rootState.App.userAgent + ) + try { + commit(MUTATION_TYPES.CHANGE_LOADING, true) + let options = { + irreversible: state.filter.irreversible, + whole_word: state.filter.whole_word + } + if (state.filter.expires_at !== null) { + options = Object.assign({}, options, { + expires_in: state.filter.expires_at + }) + } + const res = await client.createFilter(state.filter.phrase, state.filter.context, options) + dispatch('resetFilter') + return res.data + } finally { + commit(MUTATION_TYPES.CHANGE_LOADING, false) + } + } +} + +const NewFilters: Module = { + namespaced: true, + state: state, + mutations: mutations, + actions: actions +} + +export default NewFilters