mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 13:23:05 +01:00
Search for snippet by title, description and language
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
5
client/src/typescript/interfaces/SearchQuery.ts
Normal file
5
client/src/typescript/interfaces/SearchQuery.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface SearchQuery {
|
||||
query: string;
|
||||
tags: string[];
|
||||
languages: string[];
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export * from './Route';
|
||||
export * from './Response';
|
||||
export * from './Context';
|
||||
export * from './Statistics';
|
||||
export * from './SearchQuery';
|
||||
|
||||
@@ -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[] => {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
5
src/typescript/interfaces/SearchQuery.ts
Normal file
5
src/typescript/interfaces/SearchQuery.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface SearchQuery {
|
||||
query: string;
|
||||
tags: string[];
|
||||
languages: string[];
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export * from './Snippet';
|
||||
export * from './Tag';
|
||||
export * from './Snippet_Tag';
|
||||
export * from './Body';
|
||||
export * from './SearchQuery';
|
||||
|
||||
Reference in New Issue
Block a user