+
{Array.from(row.getElementsByTagName('col')).map(
(col: any, cindex: number) => (
{Array.from(col.getElementsByTagName('control')).map(
(ctrl: any, aindex: number) => (
-
+
{ctrl.getAttribute('type') === 'input' && (
-
+
)}
{ctrl.getAttribute('type') === 'hotwords' && (
-
+
)}
{ctrl.getAttribute('type') === 'indices' && (
-
+
)}
{ctrl.getAttribute('type') === 'aggregation' && (
-
+
)}
{ctrl.getAttribute('type') === 'results' && (
-
+
)}
- )
+ ),
)}
- )
+ ),
)}
))}
- )
+ );
}
diff --git a/src/components/results.tsx b/src/components/results.tsx
index 4ae3f3815f893e8e4898f6633e1563d38fed80b1..579c46ca4e75152468514a137a3cb65f6441605d 100644
--- a/src/components/results.tsx
+++ b/src/components/results.tsx
@@ -1,3 +1,5 @@
-export default function (props: any) {
+
+
+export default function () {
return
results
}
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..28822ec4abe4d529995e70ae201767ac2292e8d1
--- /dev/null
+++ b/src/hooks/index.ts
@@ -0,0 +1 @@
+export * from './useIndexeaSearch';
diff --git a/src/hooks/useIndexeaSearch.ts b/src/hooks/useIndexeaSearch.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c380af8d1c050cbef94fbab82e311dabcde6ef5d
--- /dev/null
+++ b/src/hooks/useIndexeaSearch.ts
@@ -0,0 +1,261 @@
+import { WidgetBean } from '@indexea/sdk';
+import { SearchContext, SearchFilter } from '@indexea/sdk/context';
+import { WidgetLayout } from '@indexea/sdk/layout';
+import {
+ createContext,
+ Dispatch,
+ ReactNode,
+ SetStateAction,
+ useCallback,
+ useContext,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+import { Indexea } from '../openapi';
+
+export type IndexeaSearchResults = object;
+
+export type UseInitIndexeaSearchProps = {
+ indexea: Indexea
+ widget: WidgetBean
+}
+
+export type IndexeaInputAttr = {
+ type: 'input',
+ button: boolean,
+ complete: string,
+ complete_count: string | number,
+ complete_url_field: string | number,
+ logo: boolean,
+ placeholder: ReactNode,
+ query: string,
+ suggest: boolean,
+}
+
+export type HotWordsAttr = {
+ type: 'hotwords',
+ scope: string,
+ show_count: number | string,
+}
+
+export type IndicesAttr = {
+ type: 'indices',
+ show_count: boolean,
+} & {
+ [k: string]: string,
+}
+
+export type AggregationAttr = {
+ type: 'aggregation',
+ show_count: boolean,
+} & {
+ [k: string]: string,
+}
+
+
+export type IndexeaLayoutData = {
+ data: WidgetLayout,
+ input: IndexeaInputAttr,
+ hotWords: HotWordsAttr,
+ indices: IndicesAttr,
+ aggregation: AggregationAttr
+}
+
+const defaultLayout = '
';
+
+export type IndexeaSearchContextType = {
+ context: SearchContext,
+ results: IndexeaSearchResults | undefined,
+ setResults: Dispatch
>,
+ inQuery: boolean,
+ defaultLogo: string,
+ selectLogo: (showLogo?: boolean) => string,
+ layout: IndexeaLayoutData,
+ search: (q: string, queryId?: number) => void,
+ loading: boolean,
+}
+
+// @ts-ignore context default value
+const IndexeaSearchContext = createContext({});
+
+export const IndexeaSearchProvider = IndexeaSearchContext.Provider;
+
+function prepareWidgetLayout(data: WidgetLayout): IndexeaLayoutData {
+ return {
+ data,
+ input : data.getAttributes('input'),
+ hotWords : data.getAttributes('hotwords'),
+ indices : data.getAttributes('indices'),
+ aggregation: data.getAttributes('aggregation'),
+ };
+}
+
+// 因为 SearchContext 已经封装好了,所以我就不去改动了,这里只作为对 SearchContext 的一个代理暴露
+export function useInitIndexeaSearch({
+ widget,
+ indexea,
+}: UseInitIndexeaSearchProps): IndexeaSearchContextType {
+ const locationSearch = window.location.search;
+
+ const searchRef = useRef(locationSearch);
+ const layoutRef = useRef(defaultLayout);
+
+ //全局搜索上下文
+ const [context, setContext] = useState(
+ SearchContext.fromUrl(widget, locationSearch),
+ );
+ //搜索结果
+ const [results, setResults] = useState(undefined);
+ const [layout, setLayout] = useState(prepareWidgetLayout(WidgetLayout.parse(defaultLayout)));
+ const [loading, setLoading] = useState(false);
+ const [logo, setLogo] = useState('/static/indexea.png');
+
+ const beginSearch = useCallback(function beginSearch() {
+ if (!context?.q) return;
+ // let url = context.toUrl();
+ // history.pushState({}, '', '?' + url);
+ var args: any = context.filters.reduce((obj: any, f: SearchFilter) => {
+ obj[f.name] = obj[f.name]
+ ? Array.isArray(obj[f.name])
+ ? obj[f.name].concat(f.value)
+ : [obj[f.name], f.value]
+ : f.value;
+ return obj;
+ }, {});
+ args.sort_by_f = context.sort;
+ // setResults(undefined);
+ setLoading(true);
+ indexea
+ .search(
+ context.query,
+ context.q,
+ args,
+ context.getFromIndex(),
+ context.itemsPerPage,
+ )
+ .then(setResults)
+ .catch((err: any) => {
+ console.error(err);
+ setResults(undefined);
+ })
+ .finally(() => setLoading(false))
+ ;
+ }, [context]);
+
+ const updateContext = useCallback((search: string) => {
+ if (searchRef.current !== search) {
+ searchRef.current = search;
+ const newContext = SearchContext.fromUrl(widget, search);
+ setContext(newContext);
+ }
+ }, []);
+
+ const search = useCallback(function search(q: string, queryId?: number) {
+ // loading 就不去加载
+ if (loading) return;
+ const newContext = new SearchContext(widget, q, 1, [], queryId == null ? context.query: queryId, context.sort);
+ const url = newContext.toUrl();
+ const search = !url ? '' : '?' + url;
+ history.pushState({}, '', '?' + url);
+ updateContext(search);
+ }, [beginSearch, context, loading]);
+
+ const listenPopState = useCallback(function listenPopState(event: PopStateEvent) {
+ // @ts-ignore event location
+ updateContext(event.currentTarget.location.search);
+ }, [updateContext]);
+
+ useEffect(() => {
+ updateContext(window.location.search);
+ }, [window.location.search]);
+
+ useEffect(() => {
+ beginSearch();
+ }, [context]);
+
+ useEffect(() => {
+ if (context.widget.layout !== layoutRef.current) {
+ layoutRef.current = context.widget.layout;
+ setLayout(prepareWidgetLayout(WidgetLayout.parse(context.widget.layout || defaultLayout)));
+ }
+ }, [context.widget.layout]);
+
+ useEffect(() => {
+ window.addEventListener('popstate', listenPopState);
+
+ return () => window.removeEventListener('popstate', listenPopState);
+ }, []);
+
+ const getHotWordsShowCount = useCallback(() => {
+ return (layout.hotWords.show_count as number) || 5;
+ }, [layout]);
+
+ const selectLogo = useCallback((showLogo = true) => {
+ if (showLogo && !!layout.input.logo) {
+ return logo;
+ }
+ return '';
+ }, [layout, logo]);
+
+ return {
+ context,
+ results, setResults,
+ inQuery : !!context.q,
+ defaultLogo: logo,
+ selectLogo,
+ layout,
+ search,
+ loading,
+ };
+}
+
+export function useIndexeaSearch(): IndexeaSearchContextType {
+ return useContext(IndexeaSearchContext);
+}
+
+// 以下为从首页 index.tsx 搬过来的方法,而且未实现的方法体
+/**
+ function switchQuery(query: number) {
+ if (context.query != query) {
+ context.query = query;
+ context.filters = [];
+ setContext(context);
+ beginSearch();
+ }
+ }
+
+ function sort(s: string) {
+ context.sort = s;
+ setContext(context);
+ beginSearch();
+ }
+
+ function changePage(p: number) {
+ context.page = p;
+ setContext(context);
+ beginSearch();
+ }
+
+ function addFilter(f: SearchFilter) {
+ if (f.multiple || !context.filters.find(sf => sf.name == f.name)) {
+ context.filters.push(f);
+ } else {
+ context.filters = context.filters.map(sf => (sf.name == f.name ? f : sf));
+ }
+ setContext(context);
+ beginSearch();
+ }
+
+ function deleteFilter(f: SearchFilter) {
+ context.filters = context.filters.filter(
+ tf => tf.name != f.name || tf.value != f.value,
+ );
+ setContext(context);
+ beginSearch();
+ }
+
+ function clickHit(_id: string) {
+ indexea.click(results.action, _id);
+ }
+ */