diff --git a/client/src/App.tsx b/client/src/App.tsx index 05db805..9311b4b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,5 +1,5 @@ import { BrowserRouter, Route, Switch } from 'react-router-dom'; -import { getConfig, setTheme } from './store/actions'; +import { fetchQueries, getConfig, setTheme } from './store/actions'; import 'external-svg-loader'; // Redux @@ -27,15 +27,18 @@ if (localStorage.theme) { // Check for updates checkVersion(); +// fetch queries +store.dispatch(fetchQueries()); + const App = (): JSX.Element => { return ( - - - - + + + + diff --git a/client/src/components/Settings/SearchSettings/SearchSettings.tsx b/client/src/components/Settings/SearchSettings/SearchSettings.tsx index dc3a4af..a3fec29 100644 --- a/client/src/components/Settings/SearchSettings/SearchSettings.tsx +++ b/client/src/components/Settings/SearchSettings/SearchSettings.tsx @@ -27,6 +27,7 @@ interface Props { createNotification: (notification: NewNotification) => void; updateConfig: (formData: SearchForm) => void; loading: boolean; + customQueries: Query[]; } const SearchSettings = (props: Props): JSX.Element => { @@ -81,7 +82,7 @@ const SearchSettings = (props: Props): JSX.Element => { value={formData.defaultSearchProvider} onChange={(e) => inputChangeHandler(e)} > - {queries.map((query: Query, idx) => ( + {[...queries, ...props.customQueries].map((query: Query, idx) => ( @@ -122,6 +123,7 @@ const SearchSettings = (props: Props): JSX.Element => { const mapStateToProps = (state: GlobalState) => { return { loading: state.config.loading, + customQueries: state.config.customQueries, }; }; diff --git a/client/src/store/actions/actionTypes.ts b/client/src/store/actions/actionTypes.ts index 4324834..0c1cc87 100644 --- a/client/src/store/actions/actionTypes.ts +++ b/client/src/store/actions/actionTypes.ts @@ -26,8 +26,9 @@ import { ClearNotificationAction, // Config GetConfigAction, - UpdateConfigAction + UpdateConfigAction, } from './'; +import { FetchQueriesAction } from './config'; export enum ActionTypes { // Theme @@ -62,35 +63,37 @@ export enum ActionTypes { clearNotification = 'CLEAR_NOTIFICATION', // Config getConfig = 'GET_CONFIG', - updateConfig = 'UPDATE_CONFIG' + updateConfig = 'UPDATE_CONFIG', + fetchQueries = 'FETCH_QUERIES', } -export type Action = +export type Action = // Theme - SetThemeAction | + | SetThemeAction // Apps - GetAppsAction | - PinAppAction | - AddAppAction | - DeleteAppAction | - UpdateAppAction | - ReorderAppsAction | - SortAppsAction | + | GetAppsAction + | PinAppAction + | AddAppAction + | DeleteAppAction + | UpdateAppAction + | ReorderAppsAction + | SortAppsAction // Categories - GetCategoriesAction | - AddCategoryAction | - PinCategoryAction | - DeleteCategoryAction | - UpdateCategoryAction | - SortCategoriesAction | - ReorderCategoriesAction | + | GetCategoriesAction + | AddCategoryAction + | PinCategoryAction + | DeleteCategoryAction + | UpdateCategoryAction + | SortCategoriesAction + | ReorderCategoriesAction // Bookmarks - AddBookmarkAction | - DeleteBookmarkAction | - UpdateBookmarkAction | + | AddBookmarkAction + | DeleteBookmarkAction + | UpdateBookmarkAction // Notifications - CreateNotificationAction | - ClearNotificationAction | + | CreateNotificationAction + | ClearNotificationAction // Config - GetConfigAction | - UpdateConfigAction; \ No newline at end of file + | GetConfigAction + | UpdateConfigAction + | FetchQueriesAction; diff --git a/client/src/store/actions/config.ts b/client/src/store/actions/config.ts index a14e21e..baddbe5 100644 --- a/client/src/store/actions/config.ts +++ b/client/src/store/actions/config.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { Dispatch } from 'redux'; import { ActionTypes } from './actionTypes'; -import { Config, ApiResponse } from '../../interfaces'; +import { Config, ApiResponse, Query } from '../../interfaces'; import { CreateNotificationAction } from './notification'; import { searchConfig } from '../../utility'; @@ -13,18 +13,18 @@ export interface GetConfigAction { export const getConfig = () => async (dispatch: Dispatch) => { try { const res = await axios.get>('/api/config'); - + dispatch({ type: ActionTypes.getConfig, - payload: res.data.data - }) + payload: res.data.data, + }); // Set custom page title if set document.title = searchConfig('customTitle', 'Flame'); } catch (err) { - console.log(err) + console.log(err); } -} +}; export interface UpdateConfigAction { type: ActionTypes.updateConfig; @@ -34,19 +34,41 @@ export interface UpdateConfigAction { export const updateConfig = (formData: any) => async (dispatch: Dispatch) => { try { const res = await axios.put>('/api/config', formData); + dispatch({ type: ActionTypes.createNotification, payload: { title: 'Success', - message: 'Settings updated' - } - }) + message: 'Settings updated', + }, + }); dispatch({ type: ActionTypes.updateConfig, - payload: res.data.data - }) + payload: res.data.data, + }); } catch (err) { console.log(err); } -} \ No newline at end of file +}; + +export interface FetchQueriesAction { + type: ActionTypes.fetchQueries; + payload: Query[]; +} + +export const fetchQueries = + () => async (dispatch: Dispatch) => { + try { + const res = await axios.get>( + '/api/config/0/queries' + ); + + dispatch({ + type: ActionTypes.fetchQueries, + payload: res.data.data, + }); + } catch (err) { + console.log(err); + } + }; diff --git a/client/src/store/reducers/config.ts b/client/src/store/reducers/config.ts index 071f461..93150e2 100644 --- a/client/src/store/reducers/config.ts +++ b/client/src/store/reducers/config.ts @@ -1,36 +1,50 @@ import { ActionTypes, Action } from '../actions'; -import { Config } from '../../interfaces'; +import { Config, Query } from '../../interfaces'; export interface State { loading: boolean; config: Config[]; + customQueries: Query[]; } const initialState: State = { loading: true, - config: [] -} + config: [], + customQueries: [], +}; const getConfig = (state: State, action: Action): State => { return { + ...state, loading: false, - config: action.payload - } -} + }; +}; const updateConfig = (state: State, action: Action): State => { return { ...state, - config: action.payload - } -} + config: action.payload, + }; +}; + +const fetchQueries = (state: State, action: Action): State => { + return { + ...state, + customQueries: action.payload, + }; +}; const configReducer = (state: State = initialState, action: Action) => { - switch(action.type) { - case ActionTypes.getConfig: return getConfig(state, action); - case ActionTypes.updateConfig: return updateConfig(state, action); - default: return state; + switch (action.type) { + case ActionTypes.getConfig: + return getConfig(state, action); + case ActionTypes.updateConfig: + return updateConfig(state, action); + case ActionTypes.fetchQueries: + return fetchQueries(state, action); + default: + return state; } -} +}; -export default configReducer; \ No newline at end of file +export default configReducer; diff --git a/client/src/utility/searchParser.ts b/client/src/utility/searchParser.ts index a1c3787..2befdd2 100644 --- a/client/src/utility/searchParser.ts +++ b/client/src/utility/searchParser.ts @@ -1,6 +1,6 @@ import { queries } from './searchQueries.json'; import { Query, SearchResult } from '../interfaces'; - +import { store } from '../store/store'; import { searchConfig } from '.'; export const searchParser = (searchQuery: string): SearchResult => { @@ -16,6 +16,8 @@ export const searchParser = (searchQuery: string): SearchResult => { }, }; + const customQueries = store.getState().config.customQueries; + // Check if url or ip was passed const urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?|^((http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/; @@ -33,7 +35,9 @@ export const searchParser = (searchQuery: string): SearchResult => { ? encodeURIComponent(splitQuery[2]) : encodeURIComponent(searchQuery); - const query = queries.find((q: Query) => q.prefix === prefix); + const query = [...queries, ...customQueries].find( + (q: Query) => q.prefix === prefix + ); // If search provider was found if (query) { diff --git a/controllers/config.js b/controllers/config.js index a9768d2..85b209a 100644 --- a/controllers/config.js +++ b/controllers/config.js @@ -162,7 +162,7 @@ exports.getCss = asyncWrapper(async (req, res, next) => { // @access Public exports.updateCss = asyncWrapper(async (req, res, next) => { const file = new File(join(__dirname, '../public/flame.css')); - file.write(req.body.styles); + file.write(req.body.styles, false); // Copy file to docker volume fs.copyFileSync( @@ -175,3 +175,16 @@ exports.updateCss = asyncWrapper(async (req, res, next) => { data: {}, }); }); + +// @desc Get custom queries file +// @route GET /api/config/0/queries +// @access Public +exports.getQueries = asyncWrapper(async (req, res, next) => { + const file = new File(join(__dirname, '../data/customQueries.json')); + const content = JSON.parse(file.read()); + + res.status(200).json({ + success: true, + data: content.queries, + }); +}); diff --git a/routes/config.js b/routes/config.js index eebf5dd..6abea15 100644 --- a/routes/config.js +++ b/routes/config.js @@ -10,23 +10,15 @@ const { deletePair, updateCss, getCss, + getQueries, } = require('../controllers/config'); -router - .route('/') - .post(createPair) - .get(getAllPairs) - .put(updateValues); +router.route('/').post(createPair).get(getAllPairs).put(updateValues); -router - .route('/:key') - .get(getSinglePair) - .put(updateValue) - .delete(deletePair); +router.route('/:key').get(getSinglePair).put(updateValue).delete(deletePair); -router - .route('/0/css') - .get(getCss) - .put(updateCss); +router.route('/0/css').get(getCss).put(updateCss); -module.exports = router; \ No newline at end of file +router.route('/0/queries').get(getQueries); + +module.exports = router; diff --git a/utils/File.js b/utils/File.js index 0b2fbdc..f135da8 100644 --- a/utils/File.js +++ b/utils/File.js @@ -3,7 +3,7 @@ const fs = require('fs'); class File { constructor(path) { this.path = path; - this.content = ''; + this.content = null; } read() { @@ -16,10 +16,13 @@ class File { } } - write(data) { + write(data, isJSON) { this.content = data; - fs.writeFileSync(this.path, this.content); + fs.writeFileSync( + this.path, + isJSON ? JSON.stringify(this.content) : this.content + ); } } -module.exports = File; \ No newline at end of file +module.exports = File;