使用 Frontity 实现相关文章功能
本篇文章将继续介绍使用 Frontity 增加新的网站功能,这次要实现的功能是经常会用到的如何显示一篇文章的相关文章。
首先还是要增加一个 Handler,用来异步获取相关文章列表。
sample-frontity-project/packages/frontity-chakra-theme/src/components/handler/post-related-archive.js
const PostRelatedArchive = {
pattern: "/related-posts/:typ/:pid(\\d+)",
name: "related post archive",
priority: 10,
func: async ({ link, params, state, libraries, force }) => {
const { api, populate, parse, getTotal, getTotalPages } = libraries.source;
const { page, query, route } = parse(link);
//从状态中获取当前post对象
const post = state.source[params.typ][params.pid];
var cats = [4, 36, 118]; //限定目录
var tags= [];
var endpoint = params.typ + "s";
post.format=="gallery"?cats = post.categories.filter(function (item) {return item!=3;}):tags = post.tags; //过滤掉类型是画廊的文章
const response = await api.get({
endpoint: endpoint,
params: {
_embed: true,
page,
exclude: post.id, //不包含当前的post
categories: cats,
tags: tags,
orderby: 'rand',
order: "desc",
...state.source.params
},
})
const items = await populate({
response, state, force,
});
if (page > 1 && items.length === 0) throw new ServerError(`post archive doesn't have page ${page}`, 404);
const total = getTotal(response, items.length);
const totalPages = getTotalPages(response, 0);
const hasNewerPosts = page < totalPages;
const hasOlderPosts = page > 1;
const getPageLink = (page) =>
libraries.source.stringify({
route,
query,
page,
});
const currentPageData = state.source.data[link];
const newPageData = {
type: params.typ,
items,
total,
totalPages,
isArchive: true,
isPostArchive: true,
isPostTypeArchive: true,
...(hasOlderPosts && { previous: getPageLink(page - 1) }),
...(hasNewerPosts && { next: getPageLink(page + 1) }),
};
Object.assign(currentPageData, newPageData);
}
}
export default PostRelatedArchive;
第二步显示相关文章列表,使用了ChaKra SimpleGrid 组件。
sample-frontity-project/packages/frontity-chakra-theme/src/components/post/post-related.js
import React, {useState, useEffect} from "react";
import { connect } from "frontity"
import { Box, SimpleGrid, SkeletonCircle, SkeletonText, Flex, Heading, Stack, Divider } from "@chakra-ui/react";
import Image from "@frontity/components/image";
import { formatPostData } from "../helpers";
import Link from "../link";
import {displayTextString} from "../text-string"
const PostRelated = ({state, actions, postType, postId, ...rest}) => {
const [isReady, setIsReady] = useState(false);
const link = `/related-posts/${postType}/${postId}`; //构造Url
const data = state.source.get(link);
useEffect( () => {
//如果数据没准备好,从WordPress中获取
if(!data.isReady) actions.source.fetch(link);
if(data.isReady) setIsReady(true);
}
, [data.isReady]);
if(data.is404) return null;
return (
<>
{isReady ?(
<>
<Divider borderBottom="1px solid" my="20px" />
<Stack p="5">
<Heading fontSize="x-large" as="h6" textTransform="uppercase">{displayTextString(0, "relatedPost")}</Heading>
<SimpleGrid columns={{ base: 1, md: 5 }} spacing={1} >
{data.items && data.items.map(({ type, id }) => {
const item = state.source[type][id];
const datafmt = formatPostData(state, item);
const { title, featured_media, link } = datafmt;
const { src, alt, srcSet } = featured_media;
return (
<Flex
direction="column"
position="relative"
bg="white"
as="article"
key={item.id}
{...rest}
>
{featured_media && featured_media.src && (
<Link link={link}>
<Box
role="group"
cursor="pointer"
height="150px"
width="100%"
pos="relative"
>
<Box
as={Image}
width="900"
height="550"
position="absolute"
boxSize="100%"
objectFit="cover"
top="0"
left="0"
maxWidth="100%"
src={src}
alt={alt}
srcSet={srcSet}
/>
</Box>
</Link>
)}
<Flex p="10px" flexGrow="1" direction="column">
<Heading fontSize="small" as="h6" textTransform="uppercase">
<Link link={link} dangerouslySetInnerHTML={{ __html: title }}></Link>
</Heading>
</Flex>
</Flex>
);
})}
</SimpleGrid>
</Stack>
</>
):(<Stack p="5">{/* 显示过渡动画 */}
<SkeletonCircle size="50" />
<SkeletonText mt="10" noOfLines={5} spacing="8" />
</Stack>
)}
</>
);
}
export default connect(PostRelated)
最后在显示文章界面的组件中加入相关文章组件。
sample-frontity-project/packages/frontity-chakra-theme/src/components/post/post.js
import { Box, Divider } from "@chakra-ui/react";
import { connect, styled } from "frontity";
import React, { useEffect } from "react";
import List from "../archive";
import useScrollProgress from "../hooks/useScrollProgress";
import { LightPatternBox } from "../styles/pattern-box";
import Section from "../styles/section";
import AuthorBio from "./author-bio";
import FeaturedMedia from "./featured-media";
import PostHeader from "./post-header";
import PostProgressBar from "./post-progressbar";
import { getPostData, formatPostData } from "../helpers";
import PostRelated from "./post-related";
import Comments from "../comments";
const Post = ({ state, actions, libraries }) => {
const postData = getPostData(state);
const post = formatPostData(state, postData);
// Get the html2react component.
const Html2React = libraries.html2react.Component;
// Once the post has loaded in the DOM, prefetch both the
// home posts and the list component so if the user visits
// the home page, everything is ready and it loads instantly.
useEffect(() => {
actions.source.fetch(`@comments/${postData.id}`);
//相关文章预取
actions.source.fetch(`/related-posts/${postData.type}/${postData.id}`);
actions.source.fetch("/");
List.preload();
}, []);
const [ref, scroll] = useScrollProgress();
// Load the post, but only if the data is ready.
if (!postData.isReady) return null;
return (
<LightPatternBox showPattern={state.theme.showBackgroundPattern} ref={ref}>
<Box pb={{ base: "2rem", lg: "50px" }}>
<PostHeader
mt={{ base: "20px", lg: "4rem" }}
px={{ base: "32px", md: "0" }}
categories={post.categories}
tags={post.tags}
heading={post.title}
author={post.author}
date={post.publishDate}
isPage={postData.isPage}
/>
</Box>
{!postData.isPage && <PostProgressBar value={scroll} />}
{/* Look at the settings to see if we should include the featured image */}
<Section bg="white" pb="80px" size="lg">
{post.featured_media != null && (
<FeaturedMedia id={post.featured_media.id} />
)}
{/* Render the content using the Html2React component so the HTML is processed
by the processors we included in the libraries.html2react.processors array. */}
<Content
as={Section}
px={{ base: "32px", md: "0" }}
size="md"
pt="50px"
>
<Html2React html={post.content} />
</Content>
<Comments postId={postData.id}></Comments>
{/* 调用相关文章显示组件 */}
<PostRelated postType={postData.type} postId={postData.id} />
</Section>
</LightPatternBox>
);
};
export default connect(Post);