From b95ec702c6a35d7a59fb1c0f2a631c241345ee42 Mon Sep 17 00:00:00 2001 From: shreeharsha-factly Date: Mon, 30 Aug 2021 18:28:08 +0530 Subject: [PATCH 1/4] remove validation for query --- server/service/core/action/search/route.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/service/core/action/search/route.go b/server/service/core/action/search/route.go index 12cc91c21..edb55e611 100644 --- a/server/service/core/action/search/route.go +++ b/server/service/core/action/search/route.go @@ -3,7 +3,7 @@ package search import "github.com/go-chi/chi" type searchQuery struct { - Query string `json:"q" validate:"required,min=3"` + Query string `json:"q"` Limit int64 `json:"limit" validate:"lte=20"` Filters string `json:"filters"` FacetFilters []string `json:"facetFilters"` From 1d64bc63ad660bd8f33c5385e759dd8648cca3c3 Mon Sep 17 00:00:00 2001 From: shreeharsha-factly Date: Tue, 31 Aug 2021 14:09:10 +0530 Subject: [PATCH 2/4] feat: add actions, reducers and constants for search --- studio/src/actions/search.js | 45 +++++++++++++++++ studio/src/constants/search.js | 6 +++ studio/src/reducers/index.js | 2 + studio/src/reducers/searchReducer.js | 75 ++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 studio/src/actions/search.js create mode 100644 studio/src/constants/search.js create mode 100644 studio/src/reducers/searchReducer.js diff --git a/studio/src/actions/search.js b/studio/src/actions/search.js new file mode 100644 index 000000000..fd0958b48 --- /dev/null +++ b/studio/src/actions/search.js @@ -0,0 +1,45 @@ +import axios from 'axios'; +import { + ADD_SEARCH_DETAIL, + SET_SEARCH_DETAILS_LOADING, + SEARCH_DETAILS_API, +} from '../constants/search'; +import { addErrorNotification } from './notifications'; +import getError from '../utils/getError'; + +export const getSearchDetails = (query) => { + return (dispatch, getState) => { + dispatch(loadingSearchDetails()); + + return axios + .post(SEARCH_DETAILS_API, query) + .then((response) => { + const state = getState(); + dispatch( + addSearchDetails({ + data: response.data, + formats: state.formats.details, + }), + ); + }) + .catch((error) => { + dispatch(addErrorNotification(getError(error))); + }) + .finally(() => dispatch(stopLoading())); + }; +}; + +export const addSearchDetails = (data) => ({ + type: ADD_SEARCH_DETAIL, + payload: data, +}); + +export const loadingSearchDetails = () => ({ + type: SET_SEARCH_DETAILS_LOADING, + payload: true, +}); + +export const stopLoading = () => ({ + type: SET_SEARCH_DETAILS_LOADING, + payload: false, +}); diff --git a/studio/src/constants/search.js b/studio/src/constants/search.js new file mode 100644 index 000000000..2a2d1fb07 --- /dev/null +++ b/studio/src/constants/search.js @@ -0,0 +1,6 @@ +//Actions +export const ADD_SEARCH_DETAIL = 'ADD_SEARCH_DETAIL'; +export const SET_SEARCH_DETAILS_LOADING = 'SET_SEARCH_DETAILS_LOADING'; + +//API +export const SEARCH_DETAILS_API = '/core/search'; diff --git a/studio/src/reducers/index.js b/studio/src/reducers/index.js index 939abc44a..175a59426 100644 --- a/studio/src/reducers/index.js +++ b/studio/src/reducers/index.js @@ -31,6 +31,7 @@ import info from './infoReducer'; import pages from './pagesReducer'; import webhooks from './webhooksReducer'; import profile from './profileReducer'; +import search from './searchReducer'; const appReducer = combineReducers({ admin, @@ -64,6 +65,7 @@ const appReducer = combineReducers({ events, webhooks, profile, + search, }); const rootReducer = (state, action) => { diff --git a/studio/src/reducers/searchReducer.js b/studio/src/reducers/searchReducer.js new file mode 100644 index 000000000..235012626 --- /dev/null +++ b/studio/src/reducers/searchReducer.js @@ -0,0 +1,75 @@ +import { SET_SEARCH_DETAILS_LOADING, ADD_SEARCH_DETAIL } from '../constants/search'; + +const initialState = { + req: [], + details: { + articles: [], + 'fact-checks': [], + pages: [], + claims: [], + tags: [], + categories: [], + media: [], + ratings: [], + total: 0, + }, + loading: true, +}; + +export default function authorsReducer(state = initialState, action = {}) { + switch (action.type) { + case SET_SEARCH_DETAILS_LOADING: + return { + ...state, + loading: action.payload, + }; + case ADD_SEARCH_DETAIL: + const { data, formats } = action.payload; + if (data.length === 0) { + return initialState; + } + + const result = { + articles: [], + 'fact-checks': [], + pages: [], + claims: [], + tags: [], + categories: [], + media: [], + ratings: [], + total: 0, + }; + + const kind = { + category: 'categories', + tag: 'tags', + claim: 'claims', + rating: 'ratings', + medium: 'media', + }; + + data.forEach((each) => { + if (each.kind === 'post' && each.is_page) { + if (each.status !== 'template') result.pages.push(each); + } else if (each.kind === 'post') { + if (formats[each.format_id].slug === 'article' && each.status !== 'template') { + result.articles.push(each); + } + if (formats[each.format_id].slug === 'fact-check' && each.status !== 'template') { + result['fact-checks'].push(each); + } + } else if (kind[each.kind]) { + result[kind[each.kind]].push(each); + } + }); + + return { + ...state, + details: result, + total: data.length, + }; + default: + return state; + } +} From b6ab0a7cc6ff7f3105aac004037666ba6d8767d5 Mon Sep 17 00:00:00 2001 From: shreeharsha-factly Date: Tue, 31 Aug 2021 14:09:59 +0530 Subject: [PATCH 3/4] feat: add search component --- studio/src/components/GlobalNav/Sidebar.js | 2 + studio/src/components/Search/index.js | 114 +++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 studio/src/components/Search/index.js diff --git a/studio/src/components/GlobalNav/Sidebar.js b/studio/src/components/GlobalNav/Sidebar.js index a969fbade..7f94a11bb 100644 --- a/studio/src/components/GlobalNav/Sidebar.js +++ b/studio/src/components/GlobalNav/Sidebar.js @@ -8,6 +8,7 @@ import { setCollapse } from './../../actions/sidebar'; import SpaceSelector from './SpaceSelector'; import AccountMenu from './AccountMenu'; import { AppstoreOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; +import Search from '../Search'; const { Sider } = Layout; const { SubMenu } = Menu; @@ -134,6 +135,7 @@ function Sidebar({ superOrg, permission, orgs, loading, applications }) { + { + if (query.length > 0) dispatch(getSearchDetails({ q: query })); + }, [dispatch, query]); + + const searchEntites = ['articles', 'fact-checks', 'pages', 'claims', 'categories', 'tags']; + + const { data, total, entitiesLength } = useSelector(({ search }) => { + const entitiesLength = searchEntites.map((each) => { + return search.details[each].length; + }); + return { data: search.details, total: search.total, entitiesLength: entitiesLength }; + }); + + const handleOk = () => { + setOpen(false); + }; + + const handleCancel = () => { + setOpen(false); + }; + + return ( +
{ + let isSet = false; + let indexList = selected.indexList; + let indexItem = selected.indexItem; + entitiesLength.forEach((length, index) => { + if (selected.indexList === index && selected.indexItem < length - 1 && !isSet) { + isSet = true; + indexItem = selected.indexItem + 1; + } + if (!isSet && index > selected.indexList && length !== 0) { + isSet = true; + indexItem = 0; + indexList = index; + } + }); + setSelected({ indexList, indexItem }); + }} + > + setOpen(true)} /> + +
+ { + setQuery(e.target.value); + setSelected({ indexList: 0, indexItem: 0 }); + }} + placeholder={'search articles, fact-checks, claims, categories ...'} + /> +
+ + {total > 0 && query.length > 0 ? ( +
+ {searchEntites.map((each, indexList) => + data[each].length > 0 ? ( + {each.toLocaleUpperCase()}} + dataSource={data[each]} + renderItem={(item, indexItem) => { + return ( + setOpen(false)} + > + + setSelected({ indexItem, indexList })} + > + {item.title || item.name || item.claim} + + + + ); + }} + /> + ) : null, + )} +
+ ) : null} +
+
+ ); +} + +export default Search; From 04e7bda9cd7fd890059fbf6984b07154fcb07341 Mon Sep 17 00:00:00 2001 From: Mourya Prasanth Date: Thu, 2 Sep 2021 17:16:26 +0530 Subject: [PATCH 4/4] feat: add input focus while opening search modal --- studio/src/components/GlobalNav/Sidebar.js | 23 ++++++--- studio/src/components/Search/index.js | 60 +++++++++++++--------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/studio/src/components/GlobalNav/Sidebar.js b/studio/src/components/GlobalNav/Sidebar.js index 7f94a11bb..ec5e4cde2 100644 --- a/studio/src/components/GlobalNav/Sidebar.js +++ b/studio/src/components/GlobalNav/Sidebar.js @@ -130,12 +130,23 @@ function Sidebar({ superOrg, permission, orgs, loading, applications }) { height: '100vh', }} > - -
- -
- - +
+ +
+ +
+ + +
+ { - if (query.length > 0) dispatch(getSearchDetails({ q: query })); - }, [dispatch, query]); - - const searchEntites = ['articles', 'fact-checks', 'pages', 'claims', 'categories', 'tags']; + const searchEntities = ['articles', 'fact-checks', 'pages', 'claims', 'categories', 'tags']; const { data, total, entitiesLength } = useSelector(({ search }) => { - const entitiesLength = searchEntites.map((each) => { - return search.details[each].length; + const entitiesLength = searchEntities.map((entity) => { + return search.details[entity].length; }); return { data: search.details, total: search.total, entitiesLength: entitiesLength }; }); + useEffect(() => { + if (query.length > 0) dispatch(getSearchDetails({ q: query })); + }, [dispatch, query]); + const handleOk = () => { setOpen(false); }; @@ -38,62 +40,70 @@ function Search() {
{ let isSet = false; - let indexList = selected.indexList; + let entityIndex = selected.entityIndex; let indexItem = selected.indexItem; entitiesLength.forEach((length, index) => { - if (selected.indexList === index && selected.indexItem < length - 1 && !isSet) { + if (selected.entityIndex === index && selected.indexItem < length - 1 && !isSet) { isSet = true; indexItem = selected.indexItem + 1; } - if (!isSet && index > selected.indexList && length !== 0) { + if (!isSet && index > selected.entityIndex && length !== 0) { isSet = true; indexItem = 0; - indexList = index; + entityIndex = index; } }); - setSelected({ indexList, indexItem }); + setSelected({ entityIndex, indexItem }); }} > - setOpen(true)} /> + { + setOpen(true); + setTimeout(() => inputRef.current.focus(), 0); // antd dialog prevents using inputRef directly, don't modify this while refactoring dega studio + }} + />
{ setQuery(e.target.value); - setSelected({ indexList: 0, indexItem: 0 }); + setSelected({ entityIndex: 0, indexItem: 0 }); }} + ref={inputRef} placeholder={'search articles, fact-checks, claims, categories ...'} />
{total > 0 && query.length > 0 ? (
- {searchEntites.map((each, indexList) => - data[each].length > 0 ? ( + {searchEntities.map((entity, entityIndex) => + data[entity].length > 0 ? ( {each.toLocaleUpperCase()}} - dataSource={data[each]} + key={entity} + header={

{entity.toLocaleUpperCase()}

} + dataSource={data[entity]} renderItem={(item, indexItem) => { return ( setOpen(false)} > setSelected({ indexItem, indexList })} + onMouseOver={() => setSelected({ indexItem, entityIndex })} > {item.title || item.name || item.claim}