Search for snippet by title, description and language

This commit is contained in:
unknown
2021-10-04 15:01:18 +02:00
parent 966db73238
commit 3b22cd9442
11 changed files with 93 additions and 17 deletions

View File

@@ -1,7 +1,9 @@
import { useRef, useEffect, KeyboardEvent } from 'react'; import { useRef, useEffect, KeyboardEvent, useContext } from 'react';
import { SnippetsContext } from '../store';
import { searchParser } from '../utils'; import { searchParser } from '../utils';
export const SearchBar = (): JSX.Element => { export const SearchBar = (): JSX.Element => {
const { searchSnippets } = useContext(SnippetsContext);
const inputRef = useRef<HTMLInputElement>(document.createElement('input')); const inputRef = useRef<HTMLInputElement>(document.createElement('input'));
useEffect(() => { useEffect(() => {
@@ -9,7 +11,11 @@ export const SearchBar = (): JSX.Element => {
}, [inputRef]); }, [inputRef]);
const inputHandler = (e: KeyboardEvent<HTMLInputElement>) => { const inputHandler = (e: KeyboardEvent<HTMLInputElement>) => {
const rawQuery = searchParser(inputRef.current.value); const query = searchParser(inputRef.current.value);
if (e.key === 'Enter') {
searchSnippets(query);
}
}; };
return ( return (

View File

@@ -5,7 +5,7 @@ import { SnippetGrid } from '../components/Snippets/SnippetGrid';
import { SearchBar } from '../components/SearchBar'; import { SearchBar } from '../components/SearchBar';
export const Home = (): JSX.Element => { export const Home = (): JSX.Element => {
const { snippets, getSnippets } = useContext(SnippetsContext); const { snippets, getSnippets, searchResults } = useContext(SnippetsContext);
useEffect(() => { useEffect(() => {
getSnippets(); getSnippets();
@@ -19,9 +19,9 @@ export const Home = (): JSX.Element => {
<Fragment> <Fragment>
<PageHeader title='Search' /> <PageHeader title='Search' />
<SearchBar /> <SearchBar />
{/* <div className='col-12 mb-4'> <div className='col-12 mb-4'>
<SnippetGrid snippets={snippets.filter(s => s.isPinned)} /> <SnippetGrid snippets={searchResults} />
</div> */} </div>
{snippets.some(s => s.isPinned) && ( {snippets.some(s => s.isPinned) && (
<Fragment> <Fragment>

View File

@@ -6,11 +6,13 @@ import {
Snippet, Snippet,
Response, Response,
TagCount, TagCount,
NewSnippet NewSnippet,
SearchQuery
} from '../typescript/interfaces'; } from '../typescript/interfaces';
export const SnippetsContext = createContext<Context>({ export const SnippetsContext = createContext<Context>({
snippets: [], snippets: [],
searchResults: [],
currentSnippet: null, currentSnippet: null,
tagCount: [], tagCount: [],
getSnippets: () => {}, getSnippets: () => {},
@@ -20,7 +22,8 @@ export const SnippetsContext = createContext<Context>({
updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => {}, updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => {},
deleteSnippet: (id: number) => {}, deleteSnippet: (id: number) => {},
toggleSnippetPin: (id: number) => {}, toggleSnippetPin: (id: number) => {},
countTags: () => {} countTags: () => {},
searchSnippets: (query: SearchQuery) => {}
}); });
interface Props { interface Props {
@@ -29,6 +32,7 @@ interface Props {
export const SnippetsContextProvider = (props: Props): JSX.Element => { export const SnippetsContextProvider = (props: Props): JSX.Element => {
const [snippets, setSnippets] = useState<Snippet[]>([]); const [snippets, setSnippets] = useState<Snippet[]>([]);
const [searchResults, setSearchResults] = useState<Snippet[]>([]);
const [currentSnippet, setCurrentSnippet] = useState<Snippet | null>(null); const [currentSnippet, setCurrentSnippet] = useState<Snippet | null>(null);
const [tagCount, setTagCount] = useState<TagCount[]>([]); const [tagCount, setTagCount] = useState<TagCount[]>([]);
@@ -139,8 +143,19 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
.catch(err => redirectOnError()); .catch(err => redirectOnError());
}; };
const searchSnippets = (query: SearchQuery): void => {
axios
.post<Response<Snippet[]>>('/api/snippets/search', query)
.then(res => {
setSearchResults(res.data.data);
console.log(res.data.data);
})
.catch(err => console.log(err));
};
const context = { const context = {
snippets, snippets,
searchResults,
currentSnippet, currentSnippet,
tagCount, tagCount,
getSnippets, getSnippets,
@@ -150,7 +165,8 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
updateSnippet, updateSnippet,
deleteSnippet, deleteSnippet,
toggleSnippetPin, toggleSnippetPin,
countTags countTags,
searchSnippets
}; };
return ( return (

View File

@@ -1,7 +1,8 @@
import { TagCount, NewSnippet, Snippet } from '.'; import { TagCount, NewSnippet, Snippet, SearchQuery } from '.';
export interface Context { export interface Context {
snippets: Snippet[]; snippets: Snippet[];
searchResults: Snippet[];
currentSnippet: Snippet | null; currentSnippet: Snippet | null;
tagCount: TagCount[]; tagCount: TagCount[];
getSnippets: () => void; getSnippets: () => void;
@@ -12,4 +13,5 @@ export interface Context {
deleteSnippet: (id: number) => void; deleteSnippet: (id: number) => void;
toggleSnippetPin: (id: number) => void; toggleSnippetPin: (id: number) => void;
countTags: () => void; countTags: () => void;
searchSnippets: (query: SearchQuery) => void;
} }

View File

@@ -0,0 +1,5 @@
export interface SearchQuery {
query: string;
tags: string[];
languages: string[];
}

View File

@@ -4,3 +4,4 @@ export * from './Route';
export * from './Response'; export * from './Response';
export * from './Context'; export * from './Context';
export * from './Statistics'; export * from './Statistics';
export * from './SearchQuery';

View File

@@ -1,14 +1,16 @@
export const searchParser = (rawQuery: string): void => { import { SearchQuery } from '../typescript/interfaces';
// const rawQuery = 'my search tags:ui,react lang:typescript';
export const searchParser = (rawQuery: string): SearchQuery => {
// Extract filters from query // Extract filters from query
const tags = extractFilters(rawQuery, 'tags'); const tags = extractFilters(rawQuery, 'tags');
const languages = extractFilters(rawQuery, 'lang'); const languages = extractFilters(rawQuery, 'lang');
const query = rawQuery.replaceAll(/(tags|lang):[a-zA-Z]+(,[a-zA-Z]+)*/g, ''); const query = rawQuery.replaceAll(/(tags|lang):[a-zA-Z]+(,[a-zA-Z]+)*/g, '');
console.log(tags); return {
console.log(languages); query: query.trim(),
console.log(query); tags,
languages
};
}; };
const extractFilters = (query: string, filter: string): string[] => { const extractFilters = (query: string, filter: string): string[] => {

View File

@@ -1,5 +1,5 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { QueryTypes } from 'sequelize'; import { QueryTypes, Op } from 'sequelize';
import { sequelize } from '../db'; import { sequelize } from '../db';
import { asyncWrapper } from '../middleware'; import { asyncWrapper } from '../middleware';
import { SnippetModel, Snippet_TagModel } from '../models'; import { SnippetModel, Snippet_TagModel } from '../models';
@@ -10,7 +10,7 @@ import {
Logger, Logger,
createTags createTags
} from '../utils'; } from '../utils';
import { Body } from '../typescript/interfaces'; import { Body, SearchQuery } from '../typescript/interfaces';
const logger = new Logger('snippets-controller'); const logger = new Logger('snippets-controller');
@@ -209,3 +209,39 @@ export const countTags = asyncWrapper(
}); });
} }
); );
/**
* @description Search snippets
* @route /api/snippets/search
* @request POST
*/
export const searchSnippets = asyncWrapper(
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
const { query, tags, languages } = <SearchQuery>req.body;
console.log(query, tags, languages);
const languageFilter =
languages.length > 0 ? { [Op.in]: languages } : { [Op.notIn]: languages };
const snippets = await SnippetModel.findAll({
where: {
[Op.and]: [
{
[Op.or]: [
{ title: { [Op.substring]: `${query}` } },
{ description: { [Op.substring]: `${query}` } }
]
},
{
language: languageFilter
}
]
}
});
res.status(200).json({
data: snippets
});
}
);

View File

@@ -5,6 +5,7 @@ import {
deleteSnippet, deleteSnippet,
getAllSnippets, getAllSnippets,
getSnippet, getSnippet,
searchSnippets,
updateSnippet updateSnippet
} from '../controllers/snippets'; } from '../controllers/snippets';
import { requireBody } from '../middleware'; import { requireBody } from '../middleware';
@@ -23,3 +24,4 @@ snippetRouter
.delete(deleteSnippet); .delete(deleteSnippet);
snippetRouter.route('/statistics/count').get(countTags); snippetRouter.route('/statistics/count').get(countTags);
snippetRouter.route('/search').post(searchSnippets);

View File

@@ -0,0 +1,5 @@
export interface SearchQuery {
query: string;
tags: string[];
languages: string[];
}

View File

@@ -3,3 +3,4 @@ export * from './Snippet';
export * from './Tag'; export * from './Tag';
export * from './Snippet_Tag'; export * from './Snippet_Tag';
export * from './Body'; export * from './Body';
export * from './SearchQuery';