mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-15 23:16:48 +00:00
Merge pull request #200 from tangly1024/feature-notion-page-link
Feature notion page link
This commit is contained in:
@@ -90,7 +90,7 @@ yarn run start # 本地启动NextJS服务
|
||||
## 致谢
|
||||
感谢Craig Hart发起的Nobelium项目
|
||||
<table><tr align="left">
|
||||
<td align="center"><a href="https://github.com/craigary" title="Craig Hart"><img src="https://avatars.githubusercontent.com/u/10571717" width="64px;"alt="Craig Hart"/></a><br/><a href="https://notion.so/cnotion" title="Craig Hart">Craig Hart</a></td>
|
||||
<td align="center"><a href="https://github.com/craigary" title="Craig Hart"><img src="https://avatars.githubusercontent.com/u/10571717" width="64px;"alt="Craig Hart"/></a><br/><a href="https://github.com/craigary" title="Craig Hart">Craig Hart</a></td>
|
||||
</tr></table>
|
||||
|
||||
## 贡献者
|
||||
|
||||
@@ -10,9 +10,13 @@ const BLOG = {
|
||||
NOTION_ACCESS_TOKEN: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
|
||||
DEBUG: process.env.NEXT_PUBLIC_DEBUG || false, // 是否显示调试按钮
|
||||
|
||||
// 一个小插件展示你的facebook fan page~ @see https://tw.andys.pro/article/add-facebook-fanpage-notionnext
|
||||
FACEBOOK_PAGE_TITLE:
|
||||
process.env.NEXT_PUBLIC_FACEBOOK_PAGE_TITLE || null, // 邊欄 Facebook Page widget 的標題欄,填''則無標題欄 e.g FACEBOOK 粉絲團'
|
||||
FACEBOOK_PAGE:
|
||||
process.env.NEXT_PUBLIC_FACEBOOK_PAGE || null, // Facebook Page 的連結 e.g https://www.facebook.com/tw.andys.pro
|
||||
FACEBOOK_PAGE_ID: process.env.NEXT_PUBLIC_FACEBOOK_PAGE_ID || '', // Facebook Page ID 來啟用 messenger 聊天功能
|
||||
FACEBOOK_APP_ID: process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || '', // Facebook App ID 來啟用 messenger 聊天功能
|
||||
FACEBOOK_PAGE: process.env.NEXT_PUBLIC_FACEBOOK_PAGE || 'https://www.facebook.com/tw.andys.pro', // Facebook Page 的連結
|
||||
FACEBOOK_APP_ID: process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || '', // Facebook App ID 來啟用 messenger 聊天功能 获取: https://developers.facebook.com/
|
||||
|
||||
THEME: process.env.NEXT_PUBLIC_THEME || 'next', // 主题, 支持 ['next','hexo',"fukasawa','medium']
|
||||
THEME_SWITCH: process.env.NEXT_PUBLIC_THEME_SWITCH || false, // 是否显示切换主题按钮
|
||||
@@ -120,7 +124,7 @@ const BLOG = {
|
||||
process.env.NEXT_PUBLIC_DESCRIPTION || '这是一个由NotionNext生成的站点', // 站点描述,被notion中的页面描述覆盖
|
||||
|
||||
isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables)
|
||||
VERSION: '3.1.0' // 版本号
|
||||
VERSION: '3.2.1' // 版本号
|
||||
}
|
||||
|
||||
module.exports = BLOG
|
||||
|
||||
30
components/FacebookPage.js
Normal file
30
components/FacebookPage.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { FacebookProvider, Page } from 'react-facebook'
|
||||
import { FacebookIcon } from 'react-share'
|
||||
|
||||
const FacebookPage = () => {
|
||||
if (!BLOG.FACEBOOK_APP_ID && !BLOG.FACEBOOK_PAGE) {
|
||||
return <></>
|
||||
}
|
||||
return <div className="shadow-md hover:shadow-xl dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray lg:duration-100 justify-center">
|
||||
{BLOG.FACEBOOK_PAGE && (
|
||||
<div className="flex items-center pb-2">
|
||||
<a
|
||||
href={BLOG.FACEBOOK_PAGE}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-1 pr-2 pt-0"
|
||||
>
|
||||
<FacebookIcon size={28} round />
|
||||
</a>
|
||||
<a href={BLOG.FACEBOOK_PAGE} rel="noopener noreferrer" target="_blank">
|
||||
{BLOG.FACEBOOK_PAGE_TITLE}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{BLOG.FACEBOOK_APP_ID && <FacebookProvider appId={BLOG.FACEBOOK_APP_ID}>
|
||||
<Page href={BLOG.FACEBOOK_PAGE} tabs="timeline" />
|
||||
</FacebookProvider>}
|
||||
</div>
|
||||
}
|
||||
export default FacebookPage
|
||||
@@ -59,7 +59,7 @@ const NotionPage = ({ post }) => {
|
||||
(zoomRef.current).attach(imgList[i])
|
||||
}
|
||||
}
|
||||
}, [router.events])
|
||||
}, [router?.events])
|
||||
|
||||
return <div id='container'>
|
||||
<NotionRenderer
|
||||
@@ -75,8 +75,14 @@ const NotionPage = ({ post }) => {
|
||||
</div>
|
||||
}
|
||||
|
||||
/**
|
||||
* 将id映射成博文内部链接。
|
||||
* @param {*} id
|
||||
* @returns
|
||||
*/
|
||||
const mapPageUrl = id => {
|
||||
return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
// return 'https://www.notion.so/' + id.replace(/-/g, '')
|
||||
return '/article/' + id.replace(/-/g, '')
|
||||
}
|
||||
|
||||
function getMediumZoomMargin() {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { isIterable } from '../utils'
|
||||
|
||||
/**
|
||||
* 获取所有文章的分类
|
||||
* @param allPosts
|
||||
* @returns {Promise<{}|*[]>}
|
||||
*/
|
||||
export async function getAllCategories ({ allPosts, categoryOptions, sliceCount = 0 }) {
|
||||
export async function getAllCategories({ allPosts, categoryOptions, sliceCount = 0 }) {
|
||||
if (!allPosts || !categoryOptions) {
|
||||
return []
|
||||
}
|
||||
@@ -19,12 +21,14 @@ export async function getAllCategories ({ allPosts, categoryOptions, sliceCount
|
||||
}
|
||||
})
|
||||
const list = []
|
||||
categoryOptions.forEach(c => {
|
||||
const count = categoryObj[c.value]
|
||||
if (count) {
|
||||
list.push({ id: c.id, name: c.value, color: c.color, count })
|
||||
if (isIterable(categoryOptions)) {
|
||||
for (const c of categoryOptions) {
|
||||
const count = categoryObj[c.value]
|
||||
if (count) {
|
||||
list.push({ id: c.id, name: c.value, color: c.color, count })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 按照数量排序
|
||||
// list.sort((a, b) => b.count - a.count)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isIterable } from '../utils'
|
||||
|
||||
/**
|
||||
* 获取所有文章的标签
|
||||
@@ -6,7 +7,7 @@
|
||||
* @param tagOptions tags的下拉选项
|
||||
* @returns {Promise<{}|*[]>}
|
||||
*/
|
||||
export async function getAllTags ({ allPosts, sliceCount = 0, tagOptions }) {
|
||||
export async function getAllTags({ allPosts, sliceCount = 0, tagOptions }) {
|
||||
if (!allPosts || !tagOptions) {
|
||||
return []
|
||||
}
|
||||
@@ -22,12 +23,14 @@ export async function getAllTags ({ allPosts, sliceCount = 0, tagOptions }) {
|
||||
}
|
||||
})
|
||||
const list = []
|
||||
tagOptions.forEach(c => {
|
||||
const count = tagObj[c.value]
|
||||
if (count) {
|
||||
list.push({ id: c.id, name: c.value, color: c.color, count })
|
||||
}
|
||||
})
|
||||
if (isIterable(tagOptions)) {
|
||||
tagOptions.forEach(c => {
|
||||
const count = tagObj[c.value]
|
||||
if (count) {
|
||||
list.push({ id: c.id, name: c.value, color: c.color, count })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 按照数量排序
|
||||
// list.sort((a, b) => b.count - a.count)
|
||||
|
||||
@@ -11,12 +11,12 @@ const createFeedContent = async post => {
|
||||
}
|
||||
const blockMap = await getPostBlocks(post.id, 'rss-content')
|
||||
if (blockMap) {
|
||||
post.blockMap = blockMap
|
||||
const content = ReactDOMServer.renderToString(<NotionPage post={post} />)
|
||||
const regexExp =
|
||||
/<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
|
||||
return content.replace(regexExp, '')
|
||||
}
|
||||
return post.summary
|
||||
}
|
||||
|
||||
export async function generateRss(posts) {
|
||||
|
||||
11
lib/utils.js
11
lib/utils.js
@@ -64,10 +64,19 @@ export function mergeDeep(target, ...sources) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象检查
|
||||
* 是否对象
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isObject(item) {
|
||||
return (item && typeof item === 'object' && !Array.isArray(item))
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可迭代
|
||||
* @param {*} obj
|
||||
* @returns
|
||||
*/
|
||||
export function isIterable(obj) {
|
||||
return obj != null && typeof obj[Symbol.iterator] === 'function'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "notion-next",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.1",
|
||||
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -40,8 +40,10 @@
|
||||
"react-cookies": "^0.1.1",
|
||||
"react-cusdis": "^2.1.3",
|
||||
"react-dom": "17.0.2",
|
||||
"react-facebook": "^8.1.4",
|
||||
"react-messenger-customer-chat": "^0.8.0",
|
||||
"react-notion-x": "6.6.2",
|
||||
"react-share": "^4.4.0",
|
||||
"smoothscroll-polyfill": "^0.4.4",
|
||||
"typed.js": "^2.0.12",
|
||||
"use-ackee": "^3.0.0"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useGlobal } from '@/lib/global'
|
||||
import * as ThemeMap from '@/themes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { idToUuid } from 'notion-utils'
|
||||
|
||||
/**
|
||||
* 根据notion的slug访问页面
|
||||
@@ -30,7 +31,7 @@ const Slug = props => {
|
||||
}
|
||||
}, 3000)
|
||||
})
|
||||
const meta = { title: `${props?.siteInfo?.title} | loading` }
|
||||
const meta = { title: `${props?.siteInfo?.title || BLOG.TITLE} | loading` }
|
||||
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
|
||||
}
|
||||
|
||||
@@ -92,7 +93,9 @@ export async function getStaticProps({ params: { slug } }) {
|
||||
const from = `slug-props-${slug}`
|
||||
const props = await getGlobalNotionData({ from, pageType: ['Post'] })
|
||||
const allPosts = props.allPosts
|
||||
props.post = props.allPosts.find(p => p.slug === slug)
|
||||
props.post = props.allPosts.find((p) => {
|
||||
return p.slug === slug || p.id === idToUuid(slug)
|
||||
})
|
||||
if (!props.post) {
|
||||
return { props, revalidate: 1 }
|
||||
}
|
||||
|
||||
@@ -1601,7 +1601,7 @@ svg.notion-page-icon {
|
||||
}
|
||||
|
||||
svg.notion-page-icon {
|
||||
@apply hidden;
|
||||
/* @apply hidden;*/
|
||||
}
|
||||
|
||||
svg + .notion-page-title-text {
|
||||
|
||||
@@ -38,7 +38,7 @@ export const LayoutSearch = props => {
|
||||
<LayoutBase {...props}>
|
||||
<h2>Search - {keyword}</h2>
|
||||
<SearchInput {...props} />
|
||||
{postsToShow.map(p => (
|
||||
{postsToShow?.map(p => (
|
||||
<div key={p.id} className="border my-12">
|
||||
<Link href={`/article/${p.slug}`}>
|
||||
<a className="underline cursor-pointer">{p.title}</a>
|
||||
|
||||
@@ -3,6 +3,6 @@ import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutIndex = (props) => {
|
||||
return <LayoutBase {...props}>
|
||||
<BlogListPage {...props}/>
|
||||
<BlogListPage {...props} />
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -14,10 +14,9 @@ export const LayoutSearch = (props) => {
|
||||
const re = new RegExp(`${currentSearch}`, 'gim')
|
||||
container.innerHTML = container.innerHTML.replace(re, `<span class='text-red-500 border-b border-dashed'>${currentSearch}</span>`)
|
||||
}
|
||||
},
|
||||
100)
|
||||
}, 100)
|
||||
})
|
||||
return <LayoutBase {...props} currentSearch={currentSearch}>
|
||||
<BlogListPage {...props}/>
|
||||
<BlogListPage {...props} />
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import Card from './Card'
|
||||
const BlogCard = ({ post, showSummary }) => {
|
||||
const showPreview = CONFIG_FUKA.POST_LIST_PREVIEW && post.blockMap
|
||||
return (
|
||||
<Card className="w-full lg:max-w-sm p-2">
|
||||
<Card className="w-full lg:max-w-sm p-2 h-full overflow-auto">
|
||||
<div
|
||||
key={post.id}
|
||||
className="animate__animated animate__fadeIn flex flex-col-reverse justify-between duration-300"
|
||||
@@ -15,9 +15,8 @@ const BlogCard = ({ post, showSummary }) => {
|
||||
<div className="p-2 flex flex-col w-full">
|
||||
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
|
||||
<a
|
||||
className={`cursor-pointer font-bold hover:underline text-xl ${
|
||||
showPreview ? 'justify-center' : 'justify-start'
|
||||
} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}
|
||||
className={`cursor-pointer font-bold hover:underline text-xl ${showPreview ? 'justify-center' : 'justify-start'
|
||||
} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}
|
||||
>
|
||||
{post.title}
|
||||
</a>
|
||||
|
||||
@@ -17,7 +17,7 @@ const BlogListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
const showNext = page < totalPage && posts.length === BLOG.POSTS_PER_PAGE && posts.length < postCount
|
||||
const [colCount, changeCol] = useState(1)
|
||||
|
||||
function updateCol () {
|
||||
function updateCol() {
|
||||
if (window.outerWidth > 1200) {
|
||||
changeCol(3)
|
||||
} else {
|
||||
@@ -40,7 +40,7 @@ const BlogListPage = ({ page = 1, posts = [], postCount }) => {
|
||||
<div id="container">
|
||||
{/* 文章列表 */}
|
||||
<div style={{ columnCount: colCount }}>
|
||||
{posts.map(post => (
|
||||
{posts?.map(post => (
|
||||
<div key={post.id} className='justify-center flex' style={{ breakInside: 'avoid' }}>
|
||||
<BlogCard key={post.id} post={post} />
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,7 @@ const Catalog = ({ toc }) => {
|
||||
|
||||
return <div>
|
||||
<div className='w-full dark:text-gray-300 mb-2'><i className='mr-1 fas fa-stream' /> 目录</div>
|
||||
<div className='overflow-y-auto max-h-96 overscroll-none' ref={tRef}>
|
||||
<div className='overflow-y-auto max-h-96 overscroll-none scroll-hidden' ref={tRef}>
|
||||
<nav className='h-full font-sans text-black'>
|
||||
{toc.map((tocItem) => {
|
||||
const id = uuidToId(tocItem.id)
|
||||
@@ -70,11 +70,11 @@ const Catalog = ({ toc }) => {
|
||||
className={`notion-table-of-contents-item duration-300 transform font-light dark:text-gray-300
|
||||
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
|
||||
>
|
||||
<span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }}
|
||||
className={`${activeSection === id && ' font-bold text-red-400 underline'}`}
|
||||
>
|
||||
{tocItem.text}
|
||||
</span>
|
||||
<span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }}
|
||||
className={`${activeSection === id && ' font-bold text-red-400 underline'}`}
|
||||
>
|
||||
{tocItem.text}
|
||||
</span>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -11,6 +11,7 @@ import Live2D from '@/components/Live2D'
|
||||
import LoadingCover from './components/LoadingCover'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import BLOG from '@/blog.config'
|
||||
import FacebookPage from '@/components/FacebookPage'
|
||||
|
||||
/**
|
||||
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
|
||||
@@ -18,11 +19,16 @@ import BLOG from '@/blog.config'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const LayoutBase = (props) => {
|
||||
const LayoutBase = props => {
|
||||
const { children, headerSlot, floatSlot, meta, siteInfo } = props
|
||||
const [show, switchShow] = useState(false)
|
||||
// const [percent, changePercent] = useState(0) // 页面阅读百分比
|
||||
const rightAreaSlot = <Live2D />
|
||||
const rightAreaSlot = (
|
||||
<>
|
||||
<FacebookPage />
|
||||
<Live2D />
|
||||
</>
|
||||
)
|
||||
const { onLoading } = useGlobal()
|
||||
|
||||
const scrollListener = () => {
|
||||
@@ -30,7 +36,7 @@ const LayoutBase = (props) => {
|
||||
const clientHeight = targetRef?.clientHeight
|
||||
const scrollY = window.pageYOffset
|
||||
const fullHeight = clientHeight - window.outerHeight
|
||||
let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0))
|
||||
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
|
||||
if (per > 100) per = 100
|
||||
const shouldShow = scrollY > 100 && per > 0
|
||||
|
||||
@@ -45,37 +51,44 @@ const LayoutBase = (props) => {
|
||||
return () => document.removeEventListener('scroll', scrollListener)
|
||||
}, [show])
|
||||
|
||||
return (<div className='bg-hexo-background-gray dark:bg-black'>
|
||||
<CommonHead meta={meta} />
|
||||
return (
|
||||
<div className="bg-hexo-background-gray dark:bg-black">
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
<TopNav {...props} />
|
||||
<TopNav {...props} />
|
||||
|
||||
{headerSlot}
|
||||
{headerSlot}
|
||||
|
||||
<main id='wrapper' className='w-full py-8 min-h-screen'>
|
||||
<main id="wrapper" className="w-full py-8 min-h-screen">
|
||||
<div
|
||||
id="container-inner"
|
||||
className="pt-14 w-full mx-auto lg:flex justify-center lg:space-x-4"
|
||||
>
|
||||
<div className="flex-grow w-full lg:max-w-4xl">
|
||||
{onLoading ? <LoadingCover /> : children}
|
||||
</div>
|
||||
|
||||
<div id='container-inner' className='pt-14 w-full mx-auto lg:flex justify-center lg:space-x-4'>
|
||||
<div className='flex-grow w-full lg:max-w-4xl'>
|
||||
{onLoading ? <LoadingCover /> : children}
|
||||
<SideRight {...props} slot={rightAreaSlot} />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<SideRight {...props} slot={rightAreaSlot} />
|
||||
{/* 右下角悬浮 */}
|
||||
<div className="bottom-12 right-1 fixed justify-end z-20 font-sans text-white bg-indigo-500 dark:bg-hexo-black-gray rounded-sm">
|
||||
<div
|
||||
className={
|
||||
(show ? 'animate__animated ' : 'hidden') +
|
||||
' animate__fadeInUp justify-center duration-300 animate__faster flex flex-col items-center cursor-pointer '
|
||||
}
|
||||
>
|
||||
<FloatDarkModeButton />
|
||||
{floatSlot}
|
||||
<JumpToTopButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
{/* 右下角悬浮 */}
|
||||
<div className='bottom-12 right-1 fixed justify-end z-20 font-sans text-white bg-indigo-500 dark:bg-hexo-black-gray rounded-sm'>
|
||||
<div className={(show ? 'animate__animated ' : 'hidden') + ' animate__fadeInUp justify-center duration-300 animate__faster flex flex-col items-center cursor-pointer '}>
|
||||
<FloatDarkModeButton />
|
||||
{floatSlot}
|
||||
<JumpToTopButton />
|
||||
</div>
|
||||
<Footer title={siteInfo?.title || BLOG.TITLE} />
|
||||
</div>
|
||||
|
||||
<Footer title={siteInfo?.title || BLOG.TITLE} />
|
||||
|
||||
</div>)
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutBase
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import Header from './components/Header'
|
||||
import CONFIG_HEXO from './config_hexo'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutIndex = (props) => {
|
||||
return <LayoutBase {...props} headerSlot={CONFIG_HEXO.HOME_BANNER_ENABLE && <Header {...props}/>}>
|
||||
<BlogPostListPage {...props}/>
|
||||
return <LayoutBase {...props} headerSlot={CONFIG_HEXO.HOME_BANNER_ENABLE && <Header {...props} />}>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -13,23 +13,21 @@ export const LayoutSearch = props => {
|
||||
const { locale } = useGlobal()
|
||||
const router = useRouter()
|
||||
const currentSearch = keyword || router?.query?.s
|
||||
let handleTextColor = false
|
||||
const cRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
// 自动聚焦到搜索框
|
||||
cRef?.current?.focus()
|
||||
if (currentSearch && !handleTextColor) {
|
||||
if (currentSearch) {
|
||||
const targets = document.getElementsByClassName('replace')
|
||||
for (const container of targets) {
|
||||
if (container && container.innerHTML) {
|
||||
const re = new RegExp(`${currentSearch}`, 'gim')
|
||||
container.innerHTML = container.innerHTML.replace(
|
||||
re,
|
||||
`<span class='text-red-500 border-b border-dashed'>${currentSearch}</span>`
|
||||
`<span class='text-red-500 border-b border-dashed'>${currentSearch}</span>`
|
||||
)
|
||||
handleTextColor = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +44,7 @@ export const LayoutSearch = props => {
|
||||
{locale.COMMON.CATEGORY}:
|
||||
</div>
|
||||
<div id="category-list" className="duration-200 flex flex-wrap mx-8">
|
||||
{categories.map(category => {
|
||||
{categories?.map(category => {
|
||||
return (
|
||||
<Link
|
||||
key={category.name}
|
||||
@@ -73,7 +71,7 @@ export const LayoutSearch = props => {
|
||||
{locale.COMMON.TAGS}:
|
||||
</div>
|
||||
<div id="tags-list" className="duration-200 flex flex-wrap ml-8">
|
||||
{tags.map(tag => {
|
||||
{tags?.map(tag => {
|
||||
return (
|
||||
<div key={tag.name} className="p-2">
|
||||
<TagItemMini key={tag.name} tag={tag} />
|
||||
|
||||
@@ -38,13 +38,22 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{(!showPreview || showSummary) && (
|
||||
{(!showPreview || showSummary) && !post.results && (
|
||||
<p style={{ overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: '4', WebkitBoxOrient: 'vertical' }}
|
||||
className="replace h-full max-h-32 my-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
{post.summary}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* 搜索结果 */}
|
||||
{post.results && (
|
||||
<p className="mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||
{post.results.map(r => (
|
||||
<span key={r}>{r}</span>
|
||||
))}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{showPreview && (
|
||||
<div className="overflow-ellipsis truncate">
|
||||
<NotionPage post={post} />
|
||||
@@ -69,7 +78,7 @@ const BlogPostCard = ({ post, showSummary }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{CONFIG_HEXO.POST_LIST_COVER && !showPreview && post?.page_cover && (
|
||||
{CONFIG_HEXO.POST_LIST_COVER && !showPreview && post?.page_cover && !post.results && (
|
||||
<Link href={`${BLOG.SUB_PATH}/article/${post.slug}`} passHref>
|
||||
<div className="flex w-full relative duration-200 rounded-t-xl lg:rounded-r-xl lg:rounded-t-none cursor-pointer transform overflow-hidden">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
|
||||
@@ -60,9 +60,9 @@ const Catalog = ({ toc }) => {
|
||||
return <div className='px-3'>
|
||||
<div className='w-full'><i className='mr-1 fas fa-stream' /> 目录</div>
|
||||
<div className='w-full py-3'>
|
||||
<Progress/>
|
||||
<Progress />
|
||||
</div>
|
||||
<div className='overflow-y-auto max-h-36 lg:max-h-96 overscroll-none' ref={tRef}>
|
||||
<div className='overflow-y-auto max-h-36 lg:max-h-96 overscroll-none scroll-hidden' ref={tRef}>
|
||||
<nav className='h-full font-sans text-black'>
|
||||
{toc.map((tocItem) => {
|
||||
const id = uuidToId(tocItem.id)
|
||||
@@ -75,7 +75,7 @@ const Catalog = ({ toc }) => {
|
||||
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
|
||||
>
|
||||
<span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }}
|
||||
className={`${activeSection === id && ' font-bold text-indigo-400 underline'}`}
|
||||
className={`${activeSection === id && ' font-bold text-indigo-400 underline'}`}
|
||||
>
|
||||
{tocItem.text}
|
||||
</span>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import LayoutBase from './LayoutBase'
|
||||
|
||||
export const LayoutIndex = (props) => {
|
||||
return <LayoutBase {...props}>
|
||||
<BlogPostListPage {...props}/>
|
||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||
</LayoutBase>
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ const BlogPostListScroll = ({ posts = [], currentSearch }) => {
|
||||
|
||||
{/* 文章列表 */}
|
||||
<div className='space-y-1 lg:space-y-4'>
|
||||
{postsToShow.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} showSummary={true}/>
|
||||
{postsToShow?.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} showSummary={true} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -74,7 +74,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch }) => {
|
||||
<div onClick={() => {
|
||||
handleGetMore()
|
||||
}}
|
||||
className='w-full my-4 py-4 text-center cursor-pointer glassmorphism shadow-xl rounded-xl dark:text-gray-200'
|
||||
className='w-full my-4 py-4 text-center cursor-pointer dark:text-gray-200'
|
||||
> {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`} </div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,7 +95,7 @@ const getPostByPage = function (page, totalPosts, postsPerPage) {
|
||||
)
|
||||
}
|
||||
|
||||
function getSearchKey () {
|
||||
function getSearchKey() {
|
||||
const router = useRouter()
|
||||
if (router.query && router.query.s) {
|
||||
return router.query.s
|
||||
|
||||
@@ -59,9 +59,9 @@ const Catalog = ({ toc }) => {
|
||||
|
||||
return <div className='px-3'>
|
||||
<div className='w-full mt-2 mb-4'>
|
||||
<Progress/>
|
||||
<Progress />
|
||||
</div>
|
||||
<div className='overflow-y-auto max-h-96 overscroll-none' ref={tRef}>
|
||||
<div className='overflow-y-auto max-h-96 overscroll-none scroll-hidden' ref={tRef}>
|
||||
<nav className='h-full font-sans text-black'>
|
||||
{toc.map((tocItem) => {
|
||||
const id = uuidToId(tocItem.id)
|
||||
@@ -73,17 +73,17 @@ const Catalog = ({ toc }) => {
|
||||
className={`notion-table-of-contents-item duration-300 transform font-light dark:text-gray-300
|
||||
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
|
||||
>
|
||||
<span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }}
|
||||
className={`${activeSection === id && ' font-bold text-green-500 underline'}`}
|
||||
>
|
||||
{tocItem.text}
|
||||
</span>
|
||||
<span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }}
|
||||
className={`${activeSection === id && ' font-bold text-green-500 underline'}`}
|
||||
>
|
||||
{tocItem.text}
|
||||
</span>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
<JumpToTopButton className='text-gray-400 hover:text-green-500 hover:bg-gray-100 py-1 duration-200'/>
|
||||
<JumpToTopButton className='text-gray-400 hover:text-green-500 hover:bg-gray-100 py-1 duration-200' />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ const CategoryGroup = ({ currentCategory, categories }) => {
|
||||
return <div id='category-list' className='pt-4'>
|
||||
<div className='mb-2'><i className='mr-2 fas fa-th' />分类</div>
|
||||
<div className='flex flex-wrap'>
|
||||
{categories.map(category => {
|
||||
{categories?.map(category => {
|
||||
const selected = currentCategory === category.name
|
||||
return <CategoryItem key={category.name} selected={selected} category={category.name} categoryCount={category.count}/>
|
||||
return <CategoryItem key={category.name} selected={selected} category={category.name} categoryCount={category.count} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,12 +13,12 @@ const TagGroups = ({ tags, currentTag }) => {
|
||||
<div id='tags-group' className='dark:border-gray-600 py-4'>
|
||||
<div className='mb-2'><i className='mr-2 fas fa-tag' />标签</div>
|
||||
<div className='space-y-2'>
|
||||
{
|
||||
tags.map(tag => {
|
||||
const selected = tag.name === currentTag
|
||||
return <TagItemMini key={tag.name} tag={tag} selected={selected} />
|
||||
})
|
||||
}
|
||||
{
|
||||
tags?.map(tag => {
|
||||
const selected = tag.name === currentTag
|
||||
return <TagItemMini key={tag.name} tag={tag} selected={selected} />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import Card from './components/Card'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import CONFIG_NEXT from './config_next'
|
||||
import BLOG from '@/blog.config'
|
||||
|
||||
export const LayoutIndex = (props) => {
|
||||
const { latestPosts } = props
|
||||
@@ -15,7 +16,7 @@ export const LayoutIndex = (props) => {
|
||||
rightAreaSlot={rightAreaSlot}
|
||||
{...props}
|
||||
>
|
||||
{CONFIG_NEXT.POST_LIST_TYPE !== 'page'
|
||||
{BLOG.POST_LIST_STYLE !== 'page'
|
||||
? <BlogPostListScroll {...props} showSummary={true} />
|
||||
: <BlogPostListPage {...props} />
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ const Toc = ({ toc }) => {
|
||||
<div className='w-full pb-1'>
|
||||
<Progress />
|
||||
</div>
|
||||
<div className='overflow-y-auto max-h-96 overscroll-none' ref={tRef}>
|
||||
<div className='overflow-y-auto max-h-96 overscroll-none scroll-hidden' ref={tRef}>
|
||||
<nav className='h-full font-sans text-black dark:text-gray-300'>
|
||||
{toc.map((tocItem) => {
|
||||
const id = uuidToId(tocItem.id)
|
||||
|
||||
@@ -5,7 +5,6 @@ const CONFIG_NEXT = {
|
||||
|
||||
NAV_TYPE: 'normal', // ['fixed','autoCollapse','normal'] 分别是固定屏幕顶部、屏幕顶部自动折叠,不固定
|
||||
|
||||
POST_LIST_TYPE: 'page', // ['page','scroll] 文章列表样式:页码分页、单页滚动加载
|
||||
POST_LIST_COVER: false, // 文章列表显示封面图
|
||||
POST_LIST_PREVIEW: true, // 显示文章预览
|
||||
POST_LIST_SUMMARY: false, // 显示用户自定义摘要,有预览时优先只展示预览
|
||||
|
||||
Reference in New Issue
Block a user