Update search

* update algolia replace all objects
* add table of content skeleton
* add bg color
* move comment info to .env
This commit is contained in:
DefectingCat
2021-12-05 00:34:14 +08:00
parent 2f4b6d5ce7
commit 6a329d01b5
14 changed files with 186 additions and 48 deletions

View File

@ -13,10 +13,9 @@
- [x] 复制代码块内容 - [x] 复制代码块内容
- [x] Progress bar - [x] Progress bar
- [x] 提高文章页面加载速度 - [x] 提高文章页面加载速度
- [ ] 图片懒加载占位问题 - [x] ~~组件懒加载(首页,归档页,文章页)~~Suspense
- [ ] algolia 搜索样式 - [x] algolia 搜索样式
- [ ] Use [sharp](https://github.com//lovell/sharp) process images. - [ ] Use [sharp](https://github.com//lovell/sharp) process images.
- [ ] 组件懒加载(首页,归档页,文章页)
## ❤️ ## ❤️

View File

@ -212,13 +212,8 @@ const NavBar: FC = () => {
{/* Menu */} {/* Menu */}
<Box mx={['-2rem', '-4rem', 'unset']}> <Box mx={['-2rem', '-4rem', 'unset']}>
{/* Mobile menu */} {/* Mobile menu (controled by collapse) */}
<Box <Box as={Collapse} in={isOpen} animateOpacity display="none">
as={Collapse}
in={isOpen}
animateOpacity
display={[null, null, 'none']}
>
<MenuList boxBg={boxBg} handleMenuClick={handleMenuClick} /> <MenuList boxBg={boxBg} handleMenuClick={handleMenuClick} />
</Box> </Box>
{/* Desktop menu */} {/* Desktop menu */}

View File

@ -0,0 +1,26 @@
import { FC } from 'react';
import { Box, Skeleton } from '@chakra-ui/react';
const PostLoadingTOC: FC = () => {
return (
<>
<Box
display={['none', 'none', 'none', 'block']}
position="sticky"
top="3rem"
mt="3rem"
w="280px"
>
<Skeleton height="2rem" my="1rem" />
<Skeleton height="1rem" w="80%" mb="1rem" />
<Skeleton height="1rem" w="80%" ml="1rem" mb="1rem" />
<Skeleton height="1rem" w="80%" ml="1rem" mb="1rem" />
<Skeleton height="1rem" w="80%" mb="1rem" />
<Skeleton height="1rem" w="80%" ml="1rem" mb="1rem" />
<Skeleton height="1rem" w="80%" ml="1rem" mb="1rem" />
</Box>
</>
);
};
export default PostLoadingTOC;

View File

@ -11,9 +11,9 @@ const PostComment: FC = () => {
<Box mt="2rem"> <Box mt="2rem">
<Giscus <Giscus
repo="DefectingCat/DefectingCat.github.io" repo="DefectingCat/DefectingCat.github.io"
repoId="MDEwOlJlcG9zaXRvcnkyMzk5MTUyNzk=" repoId={process.env.REPO_ID ?? ''}
categoryId={process.env.CATEGORY_ID ?? ''}
category="Announcements" category="Announcements"
categoryId="DIC_kwDODkzRD84B_43T"
mapping="title" mapping="title"
reactionsEnabled="1" reactionsEnabled="1"
emitMetadata="0" emitMetadata="0"

View File

@ -21,15 +21,13 @@ const Highlight: FC<HighlightProvided & Props> = ({
return ( return (
<> <>
<span> {parsedHit.map((part, index) =>
{parsedHit.map((part, index) => part.isHighlighted ? (
part.isHighlighted ? ( <mark key={index}>{part.value}</mark>
<mark key={index}>{part.value}</mark> ) : (
) : ( <span key={index}>{part.value}</span>
<span key={index}>{part.value}</span> )
) )}
)}
</span>
</> </>
); );
}; };

View File

@ -1,7 +1,9 @@
import { FC } from 'react'; import { FC } from 'react';
import { HitsProvided } from 'react-instantsearch-core'; import { HitsProvided } from 'react-instantsearch-core';
import { connectHits } from 'react-instantsearch-dom'; import { connectHits } from 'react-instantsearch-dom';
import CustomHighlight from './CustomHighlight'; import dynamic from 'next/dynamic';
const HitCard = dynamic(() => import('./HitCard'));
export interface PostHits { export interface PostHits {
objectID: string; objectID: string;
@ -18,16 +20,9 @@ export interface PostHits {
const Hits: FC<HitsProvided<PostHits>> = ({ hits }) => { const Hits: FC<HitsProvided<PostHits>> = ({ hits }) => {
return ( return (
<> <>
<ol> {hits.map((item) => {
{hits.map((item) => { return <HitCard key={item.objectID} hit={item} />;
return ( })}
<li key={item.objectID}>
<CustomHighlight attribute="title" hit={item} />
<CustomHighlight attribute="desc" hit={item} />
</li>
);
})}
</ol>
</> </>
); );
}; };

View File

@ -0,0 +1,65 @@
import { FC, MouseEvent, useEffect } from 'react';
import { setFromPath } from 'features/router/routerSlice';
import useGetColors from 'lib/hooks/useGetColors';
import { useRouter } from 'next/router';
import { useAppDispatch } from 'app/hooks';
import { Box, Heading, Link } from '@chakra-ui/react';
import type { PostHits } from 'components/search/CustomHits';
import dynamic from 'next/dynamic';
const CustomHighlight = dynamic(() => import('./CustomHighlight'));
interface Props {
hit: PostHits;
}
const HitCard: FC<Props> = ({ hit }) => {
const { url } = hit;
const dispatch = useAppDispatch();
const router = useRouter();
const { boxBg, headingColor } = useGetColors();
const goToPost = (
e: MouseEvent<HTMLAnchorElement, globalThis.MouseEvent>,
url: string
) => {
e.preventDefault();
dispatch(setFromPath(location.pathname));
router.push(`/p/${url}`);
};
useEffect(() => {
router.prefetch(`/p/${url}`);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
return (
<>
<Box
as="article"
borderRadius="10px"
bg={boxBg}
overflow="hidden"
boxShadow="card"
mb="1rem"
p="1rem"
>
<Link
href={`/p/${url}`}
onClick={(e) => goToPost(e, url)}
_focus={{ boxShadow: 'unset' }}
>
<Heading color={headingColor} mb="0.6rem" size="md">
<CustomHighlight attribute="title" hit={hit} />
</Heading>
<Box>
<CustomHighlight attribute="desc" hit={hit} />
</Box>
</Link>
</Box>
</>
);
};
export default HitCard;

View File

@ -9,12 +9,16 @@ import {
ModalContent, ModalContent,
ModalOverlay, ModalOverlay,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import CustomSearchBox from './CustomSearchBox'; import useGetColors from 'lib/hooks/useGetColors';
import CustomHits from './CustomHits'; import dynamic from 'next/dynamic';
const CustomSearchBox = dynamic(() => import('./CustomSearchBox'));
const CustomHits = dynamic(() => import('./CustomHits'));
const searchClient = algoliasearch( const searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID ?? '', process.env.NEXT_PUBLIC_ALGOLIA_APP_ID ?? '',
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY ?? '' process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY ?? '',
{}
); );
interface Props { interface Props {
@ -23,15 +27,31 @@ interface Props {
} }
const Search: FC<Props> = ({ isModalOpen, onModalClose }) => { const Search: FC<Props> = ({ isModalOpen, onModalClose }) => {
const { bgColor } = useGetColors();
return ( return (
<> <>
<Modal size="xl" isOpen={isModalOpen} onClose={onModalClose}> <Modal
size="3xl"
isOpen={isModalOpen}
onClose={onModalClose}
scrollBehavior="inside"
>
<ModalOverlay /> <ModalOverlay />
<ModalContent>
<ModalBody p="0.5rem"> <ModalContent
my={['unset', '3.75rem']}
maxH={['100vh', 'calc(100% - 7.5rem)']}
>
<ModalBody p="1rem" bg={bgColor} rounded="lg">
<InstantSearch searchClient={searchClient} indexName="rua"> <InstantSearch searchClient={searchClient} indexName="rua">
<Flex justifyContent="space-between" alignItems="center"> <Flex
justifyContent="space-between"
alignItems="center"
mb="1rem"
>
<CustomSearchBox /> <CustomSearchBox />
<ModalCloseButton <ModalCloseButton
position="unset" position="unset"
display={[null, null, 'none']} display={[null, null, 'none']}

View File

@ -5,6 +5,7 @@ interface UseGetColors {
textColor: string; textColor: string;
bioColor: string; bioColor: string;
headingColor: string; headingColor: string;
bgColor: string;
giscusColor: 'light' | 'dark'; giscusColor: 'light' | 'dark';
borderColor: string; borderColor: string;
} }
@ -18,6 +19,8 @@ const useGetColors = (): UseGetColors => {
'whiteAlpha.800' 'whiteAlpha.800'
); );
const borderColor = useColorModeValue('gray.300', 'whiteAlpha.300'); const borderColor = useColorModeValue('gray.300', 'whiteAlpha.300');
// Body and search background color
const bgColor = useColorModeValue('home.bg', 'gray.800');
// comment // comment
const giscusColor = useColorModeValue('light', 'dark'); const giscusColor = useColorModeValue('light', 'dark');
@ -28,6 +31,7 @@ const useGetColors = (): UseGetColors => {
bioColor, bioColor,
headingColor, headingColor,
giscusColor, giscusColor,
bgColor,
borderColor, borderColor,
}; };
}; };

View File

@ -0,0 +1,29 @@
import { useEffect, useRef, useState } from 'react';
const useIntersection = () => {
const targetRef = useRef(null);
const [intersect, setIntersect] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
if (targetRef.current) {
setIntersect(true);
observer.unobserve(targetRef.current);
}
}
});
});
targetRef.current && observer.observe(targetRef.current);
}, []);
return {
targetRef,
intersect,
};
};
export default useIntersection;

View File

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
/** /**
* Use IntersectionObserver API to lazy load taget DOM. * Use IntersectionObserver API to lazy load taget DOM.
@ -18,10 +18,10 @@ const useLazyLoad = (src: string, blurPx = '10px') => {
setBlur('unset'); setBlur('unset');
}; };
useEffect(() => { const load = useCallback(
// Lazy load images when they entry the view port. (entries, observer) => {
const observer = new IntersectionObserver((entries, observer) => { // Lazy load images when they entry the view port.
entries.forEach((entry) => { entries.forEach((entry: IntersectionObserverEntry) => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
if (targetRef.current) { if (targetRef.current) {
setInitSrc(src); setInitSrc(src);
@ -33,7 +33,12 @@ const useLazyLoad = (src: string, blurPx = '10px') => {
} }
} }
}); });
}); },
[src]
);
useEffect(() => {
const observer = new IntersectionObserver(load);
targetRef.current && observer.observe(targetRef.current); targetRef.current && observer.observe(targetRef.current);
@ -43,7 +48,7 @@ const useLazyLoad = (src: string, blurPx = '10px') => {
cleanRef && cleanRef &&
(cleanRef as HTMLElement).removeEventListener('load', cleanBlur); (cleanRef as HTMLElement).removeEventListener('load', cleanBlur);
}; };
}, [src]); }, [load, src]);
return { return {
initSrc, initSrc,

View File

@ -80,6 +80,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
right="0" right="0"
size="xs" size="xs"
isIndeterminate isIndeterminate
zIndex="1984"
/> />
)} )}
{getLayout(<Component {...pageProps} />)} {getLayout(<Component {...pageProps} />)}

View File

@ -22,6 +22,7 @@ import { RootState } from 'app/store';
import { cleanFromPath } from 'features/router/routerSlice'; import { cleanFromPath } from 'features/router/routerSlice';
import useGetColors from 'lib/hooks/useGetColors'; import useGetColors from 'lib/hooks/useGetColors';
import { useAppSelector, useAppDispatch } from 'app/hooks'; import { useAppSelector, useAppDispatch } from 'app/hooks';
import PostLoadingTOC from 'components/loading/PostLoadingTOC';
const CopyButton = dynamic(() => import('components/post/CopyButton')); const CopyButton = dynamic(() => import('components/post/CopyButton'));
const Footer = dynamic(() => import('components/Footer')); const Footer = dynamic(() => import('components/Footer'));
@ -31,7 +32,7 @@ const PostImage = dynamic(() => import('components/post/PostImage'));
const PostComment = dynamic(() => import('components/post/PostComment')); const PostComment = dynamic(() => import('components/post/PostComment'));
const PostHead = dynamic(() => import('components/post/PostHead')); const PostHead = dynamic(() => import('components/post/PostHead'));
const PostTOC = dynamic(() => import('components/post/PostTOC'), { const PostTOC = dynamic(() => import('components/post/PostTOC'), {
loading: () => <p>...</p>, loading: () => <PostLoadingTOC />,
}); });
export async function getStaticPaths() { export async function getStaticPaths() {

View File

@ -82,7 +82,7 @@ async function getSortedPostsData() {
const index = client.initIndex('rua'); const index = client.initIndex('rua');
// save the objects! // save the objects!
const algoliaResponse = await index.saveObjects(posts); const algoliaResponse = await index.replaceAllObjects(posts);
// check the output of the response in the console // check the output of the response in the console
console.log( console.log(