diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index e8f98db4..6e99cbaa 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -1338,9 +1338,13 @@ Request:
POST /control/filtering/set_url
{
+ "url": "..."
+ "data": {
+ "name": "..."
"url": "..."
"enabled": true | false
}
+ }
Response:
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 9de02278..78ae0a2c 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -137,7 +137,9 @@
"enter_url_hint": "Enter URL",
"check_updates_btn": "Check updates",
"new_filter_btn": "New filter subscription",
+ "edit_filter_title": "Edit filter",
"enter_valid_filter_url": "Enter a valid URL to a filter subscription or a hosts file.",
+ "form_error_url_format": "Invalid url format",
"custom_filter_rules": "Custom filtering rules",
"custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.",
"examples_title": "Examples",
@@ -404,6 +406,7 @@
"domain": "Domain",
"answer": "Answer",
"filter_added_successfully": "The filter has been successfully added",
+ "filter_updated": "The filter successfully updated",
"statistics_configuration": "Statistics configuration",
"statistics_retention": "Statistics retention",
"statistics_retention_desc": "If you decrease the interval value, some data will be lost",
diff --git a/client/src/actions/filtering.js b/client/src/actions/filtering.js
index 3f0c59d9..117c22ee 100644
--- a/client/src/actions/filtering.js
+++ b/client/src/actions/filtering.js
@@ -78,10 +78,10 @@ export const toggleFilterRequest = createAction('FILTER_TOGGLE_REQUEST');
export const toggleFilterFailure = createAction('FILTER_TOGGLE_FAILURE');
export const toggleFilterSuccess = createAction('FILTER_TOGGLE_SUCCESS');
-export const toggleFilterStatus = (url, enabled) => async (dispatch) => {
+export const toggleFilterStatus = (url, data) => async (dispatch) => {
dispatch(toggleFilterRequest());
try {
- await apiClient.setFilterUrl({ url, enabled: !enabled });
+ await apiClient.setFilterUrl({ url, data });
dispatch(toggleFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
@@ -90,6 +90,24 @@ export const toggleFilterStatus = (url, enabled) => async (dispatch) => {
}
};
+export const editFilterRequest = createAction('EDIT_FILTER_REQUEST');
+export const editFilterFailure = createAction('EDIT_FILTER_FAILURE');
+export const editFilterSuccess = createAction('EDIT_FILTER_SUCCESS');
+
+export const editFilter = (url, data) => async (dispatch) => {
+ dispatch(editFilterRequest());
+ try {
+ await apiClient.setFilterUrl({ url, data });
+ dispatch(editFilterSuccess(url));
+ dispatch(toggleFilteringModal());
+ dispatch(addSuccessToast('filter_updated'));
+ dispatch(getFilteringStatus());
+ } catch (error) {
+ dispatch(addErrorToast({ error }));
+ dispatch(editFilterFailure());
+ }
+};
+
export const refreshFiltersRequest = createAction('FILTERING_REFRESH_REQUEST');
export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
diff --git a/client/src/components/Filters/Form.js b/client/src/components/Filters/Form.js
new file mode 100644
index 00000000..f53fffce
--- /dev/null
+++ b/client/src/components/Filters/Form.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Field, reduxForm } from 'redux-form';
+import { Trans, withNamespaces } from 'react-i18next';
+import flow from 'lodash/flow';
+
+import { renderInputField, required, isValidUrl } from '../../helpers/form';
+
+const Form = (props) => {
+ const {
+ t,
+ closeModal,
+ handleSubmit,
+ processingAddFilter,
+ processingConfigFilter,
+ } = props;
+
+ return (
+
+ );
+};
+
+Form.propTypes = {
+ t: PropTypes.func.isRequired,
+ closeModal: PropTypes.func.isRequired,
+ handleSubmit: PropTypes.func.isRequired,
+ processingAddFilter: PropTypes.bool.isRequired,
+ processingConfigFilter: PropTypes.bool.isRequired,
+};
+
+export default flow([
+ withNamespaces(),
+ reduxForm({
+ form: 'filterForm',
+ }),
+])(Form);
diff --git a/client/src/components/Filters/Modal.js b/client/src/components/Filters/Modal.js
index 58a41f5e..72d8d1d3 100644
--- a/client/src/components/Filters/Modal.js
+++ b/client/src/components/Filters/Modal.js
@@ -1,52 +1,28 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactModal from 'react-modal';
-import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
-import { R_URL_REQUIRES_PROTOCOL } from '../../helpers/constants';
+
+import { MODAL_TYPE } from '../../helpers/constants';
+import Form from './Form';
import '../ui/Modal.css';
ReactModal.setAppElement('#root');
-const initialState = {
- url: '',
- name: '',
- isUrlValid: false,
-};
-
class Modal extends Component {
- state = initialState;
-
- isUrlValid = url => R_URL_REQUIRES_PROTOCOL.test(url);
-
- handleUrlChange = async (e) => {
- const { value: url } = e.currentTarget;
- this.setState(...this.state, { url, isUrlValid: this.isUrlValid(url) });
- };
-
- handleNameChange = (e) => {
- const { value: name } = e.currentTarget;
- this.setState({ ...this.state, name });
- };
-
closeModal = () => {
this.props.toggleModal();
- this.setState({ ...this.state, ...initialState });
};
render() {
- const { isOpen, processingAddFilter } = this.props;
- const { isUrlValid, url, name } = this.state;
- const inputUrlClass = classnames({
- 'form-control mb-2': true,
- 'is-invalid': url.length > 0 && !isUrlValid,
- 'is-valid': url.length > 0 && isUrlValid,
- });
- const inputNameClass = classnames({
- 'form-control mb-2': true,
- 'is-valid': name.length > 0,
- });
- const isValidForSubmit = url.length > 0 && isUrlValid && name.length > 0;
+ const {
+ isOpen,
+ processingAddFilter,
+ processingConfigFilter,
+ handleSubmit,
+ modalType,
+ currentFilterData,
+ } = this.props;
return (
- new_filter_btn
+ {modalType === MODAL_TYPE.EDIT ? (
+ edit_filter_title
+ ) : (
+ new_filter_btn
+ )}
-
-
-
-
-
+
);
@@ -110,6 +63,10 @@ Modal.propTypes = {
addFilter: PropTypes.func.isRequired,
isFilterAdded: PropTypes.bool.isRequired,
processingAddFilter: PropTypes.bool.isRequired,
+ processingConfigFilter: PropTypes.bool.isRequired,
+ handleSubmit: PropTypes.func.isRequired,
+ modalType: PropTypes.string.isRequired,
+ currentFilterData: PropTypes.object.isRequired,
t: PropTypes.func.isRequired,
};
diff --git a/client/src/components/Filters/index.js b/client/src/components/Filters/index.js
index eb10434d..318b9b95 100644
--- a/client/src/components/Filters/index.js
+++ b/client/src/components/Filters/index.js
@@ -9,6 +9,8 @@ import CellWrap from '../ui/CellWrap';
import UserRules from './UserRules';
import Modal from './Modal';
+import { MODAL_TYPE } from '../../helpers/constants';
+
class Filters extends Component {
componentDidMount() {
this.props.getFilteringStatus();
@@ -22,15 +24,29 @@ class Filters extends Component {
this.props.setRules(this.props.filtering.userRules);
};
+ handleSubmit = (values) => {
+ const { name, url } = values;
+ const { filtering } = this.props;
+
+ if (filtering.modalType === MODAL_TYPE.EDIT) {
+ const data = { ...values };
+ this.props.editFilter(filtering.modalFilterUrl, data);
+ } else {
+ this.props.addFilter(url, name);
+ }
+ }
+
renderCheckbox = ({ original }) => {
const { processingConfigFilter } = this.props.filtering;
- const { url, enabled } = original;
+ const { url, name, enabled } = original;
+ const data = { name, url, enabled: !enabled };
+
return (