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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { Request, Response, NextFunction } from 'express';
import { QueryTypes } from 'sequelize';
import { QueryTypes, Op } from 'sequelize';
import { sequelize } from '../db';
import { asyncWrapper } from '../middleware';
import { SnippetModel, Snippet_TagModel } from '../models';
@@ -10,7 +10,7 @@ import {
Logger,
createTags
} from '../utils';
import { Body } from '../typescript/interfaces';
import { Body, SearchQuery } from '../typescript/interfaces';
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,
getAllSnippets,
getSnippet,
searchSnippets,
updateSnippet
} from '../controllers/snippets';
import { requireBody } from '../middleware';
@@ -23,3 +24,4 @@ snippetRouter
.delete(deleteSnippet);
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 './Snippet_Tag';
export * from './Body';
export * from './SearchQuery';