diff --git a/blog.config.js b/blog.config.js
index 82fab5a3..851b1718 100644
--- a/blog.config.js
+++ b/blog.config.js
@@ -270,6 +270,17 @@ const BLOG = {
// 星空雨特效 黑夜模式才会生效
STARRY_SKY: process.env.NEXT_PUBLIC_STARRY_SKY || false, // 开关
+ // AI 文章摘要生成
+ AI_SUMMARY_API:
+ process.env.AI_SUMMARY_API||
+ '',
+ AI_SUMMARY_KEY:
+ process.env.AI_SUMMARY_KEY ||
+ '',
+ AI_SUMMARY_CACHE_TIME: process.env.AI_SUMMARY_CACHE_TIME || 1800, // 缓存时间,单位秒
+ AI_SUMMARY_WORD_LIMIT: process.env.AI_SUMMARY_WORD_LIMIT || 1000,
+
+
// ********挂件组件相关********
// AI 文章摘要生成 @see https://docs_s.tianli0.top/
TianliGPT_CSS:
diff --git a/components/AISummary.js b/components/AISummary.js
new file mode 100644
index 00000000..29627c92
--- /dev/null
+++ b/components/AISummary.js
@@ -0,0 +1,98 @@
+import styles from './AISummary.module.css'
+import { useEffect, useState } from 'react'
+import { useGlobal } from '@/lib/global'
+
+const AISummary = ({ aiSummary }) => {
+ const { locale } = useGlobal()
+ const [summary, setSummary] = useState(aiSummary)
+
+ useEffect(() => {
+ showAiSummaryAnimation(aiSummary, setSummary)
+ }, [])
+
+ return (
+ aiSummary && (
+
+
+
+
+
{locale.AI_SUMMARY.NAME}
+
GPT
+
+
+
+ {summary}
+ {summary !== aiSummary && (
+
+ )}
+
+
+
+
+ )
+ )
+}
+
+const showAiSummaryAnimation = (rawSummary, setSummary) => {
+ if (!rawSummary) return
+ let currentIndex = 0
+ const typingDelay = 20
+ const punctuationDelayMultiplier = 6
+ let animationRunning = true
+ let lastUpdateTime = performance.now()
+ const animate = () => {
+ if (currentIndex < rawSummary.length && animationRunning) {
+ const currentTime = performance.now()
+ const timeDiff = currentTime - lastUpdateTime
+
+ const letter = rawSummary.slice(currentIndex, currentIndex + 1)
+ const isPunctuation = /[,。!、?,.!?]/.test(letter)
+ const delay = isPunctuation
+ ? typingDelay * punctuationDelayMultiplier
+ : typingDelay
+
+ if (timeDiff >= delay) {
+ setSummary(rawSummary.slice(0, currentIndex + 1))
+ lastUpdateTime = currentTime
+ currentIndex++
+
+ if (currentIndex < rawSummary.length) {
+ setSummary(rawSummary.slice(0, currentIndex))
+ } else {
+ setSummary(rawSummary)
+ observer.disconnect()
+ }
+ }
+ requestAnimationFrame(animate)
+ }
+ }
+ animate(rawSummary)
+ const observer = new IntersectionObserver(
+ entries => {
+ animationRunning = entries[0].isIntersecting
+ if (animationRunning && currentIndex === 0) {
+ setTimeout(() => {
+ requestAnimationFrame(animate)
+ }, 200)
+ }
+ },
+ { threshold: 0 }
+ )
+ let post_ai = document.querySelector('.post-ai')
+ if (post_ai) {
+ observer.observe(post_ai)
+ }
+}
+
+export default AISummary
diff --git a/components/AISummary.module.css b/components/AISummary.module.css
new file mode 100644
index 00000000..b7e89f2a
--- /dev/null
+++ b/components/AISummary.module.css
@@ -0,0 +1,53 @@
+.post-ai {
+ font-family: 'Noto Sans SC', sans-serif;
+ margin-bottom: 20px;
+}
+.ai-container {
+ background: linear-gradient(135deg, #f9f9f9 0%, #f5f5f5 100%);
+ border: 1px solid #e8e8e8;
+ border-radius: 10px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+}
+.ai-header {
+ background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
+ color: white;
+ padding: 12px 20px;
+ display: flex;
+ align-items: center;
+}
+.ai-icon {
+ margin-right: 10px;
+}
+.ai-title {
+ font-size: 18px;
+ font-weight: bold;
+ flex-grow: 1;
+}
+.ai-tag {
+ background-color: rgba(255, 255, 255, 0.2);
+ padding: 3px 8px;
+ border-radius: 12px;
+ font-size: 12px;
+}
+.ai-content {
+ padding: 20px;
+}
+.ai-explanation {
+ font-size: 16px;
+ line-height: 1.6;
+ color: #333;
+}
+.blinking-cursor {
+ display: inline-block;
+ width: 2px;
+ height: 20px;
+ background-color: #333;
+ animation: blink 0.7s infinite;
+ margin-left: 5px;
+}
+@keyframes blink {
+ 0% { opacity: 0; }
+ 50% { opacity: 1; }
+ 100% { opacity: 0; }
+}
diff --git a/components/WordCount.js b/components/WordCount.js
index 7f7066cf..af808251 100644
--- a/components/WordCount.js
+++ b/components/WordCount.js
@@ -1,67 +1,23 @@
import { useGlobal } from '@/lib/global'
-import { useEffect } from 'react'
/**
* 字数统计
* @returns
*/
-export default function WordCount() {
+export default function WordCount({ wordCount, readTime }) {
const { locale } = useGlobal()
- useEffect(() => {
- countWords()
- })
-
- return
-
-
- 0
-
-
-
-
- 0 {locale.COMMON.MINUTE}
-
+ return (
+
+
+
+ {locale.COMMON.WORD_COUNT}
+ {wordCount}
+
+
+
+ {locale.COMMON.READ_TIME}≈
+ {readTime} {locale.COMMON.MINUTE}
+
-}
-
-/**
- * 更新字数统计和阅读时间
- */
-function countWords() {
- const articleText = deleteHtmlTag(document.querySelector('#article-wrapper #notion-article')?.innerHTML)
- const wordCount = fnGetCpmisWords(articleText)
- // 阅读速度 300-500每分钟
- document.getElementById('wordCount').innerHTML = wordCount
- document.getElementById('readTime').innerHTML = Math.floor(wordCount / 400) + 1
- const wordCountWrapper = document.getElementById('wordCountWrapper')
- wordCountWrapper.classList.remove('hidden')
-}
-
-// 去除html标签
-function deleteHtmlTag(str) {
- if (!str) {
- return ''
- }
- str = str.replace(/<[^>]+>|&[^>]+;/g, '').trim()// 去掉所有的html标签和 之类的特殊符合
- return str
-}
-
-// 用word方式计算正文字数
-function fnGetCpmisWords(str) {
- if (!str) {
- return 0
- }
- let sLen = 0
- try {
- // eslint-disable-next-line no-irregular-whitespace
- str = str.replace(/(\r\n+|\s+| +)/g, '龘')
- // eslint-disable-next-line no-control-regex
- str = str.replace(/[\x00-\xff]/g, 'm')
- str = str.replace(/m+/g, '*')
- str = str.replace(/龘+/g, '')
- sLen = str.length
- } catch (e) {
-
- }
- return sLen
-}
+ )
+}
\ No newline at end of file
diff --git a/lib/cache/cache_manager.js b/lib/cache/cache_manager.js
index 1d665ca1..f35a1fe1 100644
--- a/lib/cache/cache_manager.js
+++ b/lib/cache/cache_manager.js
@@ -20,12 +20,12 @@ export async function getDataFromCache(key, force) {
}
}
-export async function setDataToCache(key, data) {
+export async function setDataToCache(key, data, customCacheTime) {
if (!data) {
return
}
// console.trace('[API-->>缓存写入]:', key)
- await getApi().setCache(key, data)
+ await getApi().setCache(key, data, customCacheTime)
}
export async function delCacheData(key) {
diff --git a/lib/cache/memory_cache.js b/lib/cache/memory_cache.js
index 1a7012f9..001666c3 100644
--- a/lib/cache/memory_cache.js
+++ b/lib/cache/memory_cache.js
@@ -7,8 +7,8 @@ export async function getCache(key, options) {
return await cache.get(key)
}
-export async function setCache(key, data) {
- await cache.put(key, data, cacheTime * 1000)
+export async function setCache(key, data, customCacheTime) {
+ await cache.put(key, data, (customCacheTime || cacheTime) * 1000)
}
export async function delCache(key) {
diff --git a/lib/config.js b/lib/config.js
index bf03f95f..314e8318 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -38,9 +38,10 @@ export const siteConfig = (key, defaultVal = null, extendConfig = {}) => {
case 'TAG_SORT_BY_COUNT':
case 'THEME':
case 'LINK':
- case 'NPM_CDN_BASE':
- case 'CDNJS_CDN_BASE':
- case 'JSDELIVR_CDN_BASE':
+ case 'AI_SUMMARY_API':
+ case 'AI_SUMMARY_KEY':
+ case 'AI_SUMMARY_CACHE_TIME':
+ case 'AI_SUMMARY_WORD_LIMIT':
// LINK比较特殊,
if (key === 'LINK') {
if (!extendConfig || Object.keys(extendConfig).length === 0) {
diff --git a/lib/lang/en-US.js b/lib/lang/en-US.js
index 17be1575..1649c6d3 100644
--- a/lib/lang/en-US.js
+++ b/lib/lang/en-US.js
@@ -87,5 +87,8 @@ export default {
SUBSCRIBE: 'Subscribe',
MSG: 'Get the latest news and articles to your inbox every month.',
EMAIL: 'Email'
+ },
+ AI_SUMMARY: {
+ NAME: 'AI intelligent summary',
}
}
diff --git a/lib/lang/fr-FR.js b/lib/lang/fr-FR.js
index 2bc40057..e507dc4b 100644
--- a/lib/lang/fr-FR.js
+++ b/lib/lang/fr-FR.js
@@ -21,7 +21,7 @@ export default {
URL_COPIED: "L'URL est copé!",
TABLE_OF_CONTENTS: 'Sommaire',
RELATE_POSTS: 'Article similaire',
- COPYRIGHT: 'Droit d\'auteur',
+ COPYRIGHT: "Droit d'auteur",
AUTHOR: 'Auteur',
URL: 'Link',
ANALYTICS: 'Analytique',
@@ -29,7 +29,8 @@ export default {
ARTICLE: 'Article(s)',
VISITORS: 'Visiteurs',
VIEWS: 'Views',
- COPYRIGHT_NOTICE: 'Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International (CC BY-NC-SA 4.0)',
+ COPYRIGHT_NOTICE:
+ 'Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International (CC BY-NC-SA 4.0)',
RESULT_OF_SEARCH: 'Résultats',
ARTICLE_DETAIL: 'Plus de détails',
PASSWORD_ERROR: 'Mot de passe est incorrect!',
@@ -50,5 +51,8 @@ export default {
POST: {
BACK: 'Page precedente',
TOP: 'Haut'
+ },
+ AI_SUMMARY: {
+ NAME: "Résumé intelligent par l'IA",
}
}
diff --git a/lib/lang/ja-JP.js b/lib/lang/ja-JP.js
index 628b28b3..7ce1270c 100644
--- a/lib/lang/ja-JP.js
+++ b/lib/lang/ja-JP.js
@@ -58,5 +58,8 @@ export default {
POST: {
BACK: '前のページに戻る',
TOP: '上に戻る'
+ },
+ AI_SUMMARY: {
+ NAME: 'AIインテリジェントサマリー',
}
}
diff --git a/lib/lang/tr-TR.js b/lib/lang/tr-TR.js
index 5be7a520..7b5ac6f7 100644
--- a/lib/lang/tr-TR.js
+++ b/lib/lang/tr-TR.js
@@ -53,5 +53,8 @@ export default {
POST: {
BACK: 'Geri',
TOP: 'Yukarı'
+ },
+ AI_SUMMARY: {
+ NAME: 'Yapay Zeka Akıllı Özet',
}
}
diff --git a/lib/lang/zh-CN.js b/lib/lang/zh-CN.js
index f17e0f0f..265bb0c2 100644
--- a/lib/lang/zh-CN.js
+++ b/lib/lang/zh-CN.js
@@ -87,5 +87,8 @@ export default {
SUBSCRIBE: '邮件订阅',
MSG: '订阅以获取每月更新的新闻和文章,直接发送至您的邮箱。',
EMAIL: '邮箱'
+ },
+ AI_SUMMARY: {
+ NAME: 'AI智能摘要',
}
}
diff --git a/lib/lang/zh-HK.js b/lib/lang/zh-HK.js
index e4de0f4e..a899fdac 100644
--- a/lib/lang/zh-HK.js
+++ b/lib/lang/zh-HK.js
@@ -52,5 +52,8 @@ export default {
POST: {
BACK: '返回',
TOP: '回到頂端'
+ },
+ AI_SUMMARY: {
+ NAME: 'AI智能摘要',
}
}
diff --git a/lib/lang/zh-TW.js b/lib/lang/zh-TW.js
index 02c3ed2f..e6c4f061 100644
--- a/lib/lang/zh-TW.js
+++ b/lib/lang/zh-TW.js
@@ -52,5 +52,8 @@ export default {
POST: {
BACK: '返回',
TOP: '回到頂端'
+ },
+ AI_SUMMARY: {
+ NAME: 'AI智能摘要',
}
}
diff --git a/lib/plugins/aiSummary.js b/lib/plugins/aiSummary.js
new file mode 100644
index 00000000..336b9954
--- /dev/null
+++ b/lib/plugins/aiSummary.js
@@ -0,0 +1,32 @@
+/**
+ * get Ai summary
+ * @returns {Promise}
+ * @param aiSummaryAPI
+ * @param aiSummaryKey
+ * @param truncatedText
+ */
+export async function getAiSummary(aiSummaryAPI, aiSummaryKey, truncatedText) {
+ try {
+ console.log('请求文章摘要', truncatedText.slice(0, 100))
+ const response = await fetch(aiSummaryAPI, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ token: aiSummaryKey,
+ content: truncatedText
+ })
+ })
+
+ if (response.ok) {
+ const data = await response.json()
+ return data.summary
+ } else {
+ throw new Error('Response not ok')
+ }
+ } catch (error) {
+ console.error('ChucklePostAI:请求失败', error)
+ return '获取文章摘要失败,请稍后再试。'
+ }
+}
diff --git a/lib/plugins/wordCount.js b/lib/plugins/wordCount.js
new file mode 100644
index 00000000..156c8b85
--- /dev/null
+++ b/lib/plugins/wordCount.js
@@ -0,0 +1,27 @@
+/**
+ * 更新字数统计和阅读时间
+ */
+export function countWords(pageContentText) {
+ const wordCount = fnGetCpmisWords(pageContentText)
+ // 阅读速度 300-500每分钟
+ const readTime = Math.floor(wordCount / 400) + 1
+ return { wordCount, readTime }
+}
+
+// 用word方式计算正文字数
+function fnGetCpmisWords(str) {
+ if (!str) {
+ return 0
+ }
+ let sLen = 0
+ try {
+ // eslint-disable-next-line no-irregular-whitespace
+ str = str.replace(/(\r\n+|\s+| +)/g, '龘')
+ // eslint-disable-next-line no-control-regex
+ str = str.replace(/[\x00-\xff]/g, 'm')
+ str = str.replace(/m+/g, '*')
+ str = str.replace(/龘+/g, '')
+ sLen = str.length
+ } catch (e) {}
+ return sLen
+}
diff --git a/lib/utils/post.js b/lib/utils/post.js
index e6402a02..a3ee0f91 100644
--- a/lib/utils/post.js
+++ b/lib/utils/post.js
@@ -2,6 +2,15 @@
* 文章相关工具
*/
import { checkStartWithHttp } from '.'
+import { getPostBlocks } from '@/lib/db/getSiteData'
+import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
+import { siteConfig } from '@/lib/config'
+import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
+import { getPageContentText } from '@/pages/search/[keyword]'
+import { getAiSummary } from '@/lib/plugins/aiSummary'
+import BLOG from '@/blog.config'
+import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
+import { countWords } from '@/lib/plugins/wordCount'
/**
* 获取文章的关联推荐文章列表,目前根据标签关联性筛选
@@ -88,3 +97,87 @@ export function checkSlugHasMorThanTwoSlash(row) {
!checkStartWithHttp(slug)
)
}
+
+/**
+ * 获取文章摘要
+ * @param props
+ * @param pageContentText
+ * @returns {Promise}
+ */
+async function getPageAISummary(props, pageContentText) {
+ const aiSummaryAPI = siteConfig('AI_SUMMARY_API')
+ if (aiSummaryAPI) {
+ const post = props.post
+ const cacheKey = `ai_summary_${post.id}`
+ let aiSummary = await getDataFromCache(cacheKey)
+ if (aiSummary) {
+ props.post.aiSummary = aiSummary
+ } else {
+ const aiSummaryKey = siteConfig('AI_SUMMARY_KEY')
+ const aiSummaryCacheTime = siteConfig('AI_SUMMARY_CACHE_TIME')
+ const wordLimit = siteConfig('AI_SUMMARY_WORD_LIMIT', '1000')
+ let content = ''
+ for (let heading of post.toc) {
+ content += heading.text + ' '
+ }
+ content += pageContentText
+ const combinedText = post.title + ' ' + content
+ const truncatedText = combinedText.slice(0, wordLimit)
+ aiSummary = await getAiSummary(aiSummaryAPI, aiSummaryKey, truncatedText)
+ await setDataToCache(cacheKey, aiSummary, aiSummaryCacheTime)
+ props.post.aiSummary = aiSummary
+ }
+ }
+}
+
+/**
+ * 处理文章数据
+ * @param props
+ * @param from
+ * @returns {Promise}
+ */
+export async function processPostData(props, from) {
+ // 文章内容加载
+ if (!props?.post?.blockMap) {
+ props.post.blockMap = await getPostBlocks(props.post.id, from)
+ }
+
+ if (props.post?.blockMap?.block) {
+ // 目录默认加载
+ props.post.content = Object.keys(props.post.blockMap.block).filter(
+ key => props.post.blockMap.block[key]?.value?.parent_id === props.post.id
+ )
+ props.post.toc = getPageTableOfContents(props.post, props.post.blockMap)
+ const pageContentText = getPageContentText(props.post, props.post.blockMap)
+ const { wordCount, readTime } = countWords(pageContentText)
+ props.post.wordCount = wordCount
+ props.post.readTime = readTime
+ await getPageAISummary(props, pageContentText)
+ }
+
+ // 生成全文索引 && JSON.parse(BLOG.ALGOLIA_RECREATE_DATA)
+ if (BLOG.ALGOLIA_APP_ID) {
+ uploadDataToAlgolia(props?.post)
+ }
+
+ // 推荐关联文章处理
+ const allPosts = props.allPages?.filter(
+ page => page.type === 'Post' && page.status === 'Published'
+ )
+ if (allPosts && allPosts.length > 0) {
+ const index = allPosts.indexOf(props.post)
+ props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
+ props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
+ props.recommendPosts = getRecommendPost(
+ props.post,
+ allPosts,
+ siteConfig('POST_RECOMMEND_COUNT')
+ )
+ } else {
+ props.prev = null
+ props.next = null
+ props.recommendPosts = []
+ }
+
+ delete props.allPages
+}
diff --git a/pages/[prefix]/[slug]/[...suffix].js b/pages/[prefix]/[slug]/[...suffix].js
index 90ca5a15..5e55ef85 100644
--- a/pages/[prefix]/[slug]/[...suffix].js
+++ b/pages/[prefix]/[slug]/[...suffix].js
@@ -1,9 +1,7 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
-import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData'
-import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
-import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
-import { checkSlugHasMorThanTwoSlash, getRecommendPost } from '@/lib/utils/post'
+import { getGlobalData, getPost } from '@/lib/db/getSiteData'
+import { checkSlugHasMorThanTwoSlash, processPostData } from '@/lib/utils/post'
import { idToUuid } from 'notion-utils'
import Slug from '..'
@@ -79,59 +77,12 @@ export async function getStaticProps({
}
}
- // 无法获取文章
if (!props?.post) {
+ // 无法获取文章
props.post = null
- return {
- props,
- revalidate: process.env.EXPORT
- ? undefined
- : siteConfig(
- 'NEXT_REVALIDATE_SECOND',
- BLOG.NEXT_REVALIDATE_SECOND,
- props.NOTION_CONFIG
- )
- }
- }
-
- // 文章内容加载
- if (!props?.post?.blockMap) {
- props.post.blockMap = await getPostBlocks(props.post.id, from)
- }
-
- // 目录默认加载
- if (props.post?.blockMap?.block) {
- props.post.content = Object.keys(props.post.blockMap.block).filter(
- key => props.post.blockMap.block[key]?.value?.parent_id === props.post.id
- )
- props.post.toc = getPageTableOfContents(props.post, props.post.blockMap)
- }
-
- // 生成全文索引 && JSON.parse(BLOG.ALGOLIA_RECREATE_DATA)
- if (BLOG.ALGOLIA_APP_ID) {
- uploadDataToAlgolia(props?.post)
- }
-
- // 推荐关联文章处理
- const allPosts = props.allPages?.filter(
- page => page.type === 'Post' && page.status === 'Published'
- )
- if (allPosts && allPosts.length > 0) {
- const index = allPosts.indexOf(props.post)
- props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
- props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
- props.recommendPosts = getRecommendPost(
- props.post,
- allPosts,
- siteConfig('POST_RECOMMEND_COUNT')
- )
} else {
- props.prev = null
- props.next = null
- props.recommendPosts = []
+ await processPostData(props, from)
}
-
- delete props.allPages
return {
props,
revalidate: process.env.EXPORT
diff --git a/pages/[prefix]/[slug]/index.js b/pages/[prefix]/[slug]/index.js
index cdf99afc..cb81cfd6 100644
--- a/pages/[prefix]/[slug]/index.js
+++ b/pages/[prefix]/[slug]/index.js
@@ -1,9 +1,7 @@
import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config'
-import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData'
-import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
-import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
-import { checkSlugHasOneSlash, getRecommendPost } from '@/lib/utils/post'
+import { getGlobalData, getPost } from '@/lib/db/getSiteData'
+import { checkSlugHasOneSlash, processPostData } from '@/lib/utils/post'
import { idToUuid } from 'notion-utils'
import Slug from '..'
@@ -68,59 +66,12 @@ export async function getStaticProps({ params: { prefix, slug }, locale }) {
}
}
- // 无法获取文章
if (!props?.post) {
+ // 无法获取文章
props.post = null
- return {
- props,
- revalidate: process.env.EXPORT
- ? undefined
- : siteConfig(
- 'NEXT_REVALIDATE_SECOND',
- BLOG.NEXT_REVALIDATE_SECOND,
- props.NOTION_CONFIG
- )
- }
- }
-
- // 文章内容加载
- if (!props?.post?.blockMap) {
- props.post.blockMap = await getPostBlocks(props.post.id, from)
- }
-
- // 目录默认加载
- if (props.post?.blockMap?.block) {
- props.post.content = Object.keys(props.post.blockMap.block).filter(
- key => props.post.blockMap.block[key]?.value?.parent_id === props.post.id
- )
- props.post.toc = getPageTableOfContents(props.post, props.post.blockMap)
- }
-
- // 生成全文索引 && JSON.parse(BLOG.ALGOLIA_RECREATE_DATA)
- if (BLOG.ALGOLIA_APP_ID) {
- uploadDataToAlgolia(props?.post)
- }
-
- // 推荐关联文章处理
- const allPosts = props.allPages?.filter(
- page => page.type === 'Post' && page.status === 'Published'
- )
- if (allPosts && allPosts.length > 0) {
- const index = allPosts.indexOf(props.post)
- props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
- props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
- props.recommendPosts = getRecommendPost(
- props.post,
- allPosts,
- siteConfig('POST_RECOMMEND_COUNT')
- )
} else {
- props.prev = null
- props.next = null
- props.recommendPosts = []
+ await processPostData(props, from)
}
-
- delete props.allPages
return {
props,
revalidate: process.env.EXPORT
diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js
index e26a95a9..672e6a0d 100644
--- a/pages/[prefix]/index.js
+++ b/pages/[prefix]/index.js
@@ -2,12 +2,11 @@ import BLOG from '@/blog.config'
import useNotification from '@/components/Notification'
import OpenWrite from '@/components/OpenWrite'
import { siteConfig } from '@/lib/config'
-import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData'
+import { getGlobalData, getPost } from '@/lib/db/getSiteData'
import { useGlobal } from '@/lib/global'
import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
import { getPasswordQuery } from '@/lib/password'
-import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
-import { checkSlugHasNoSlash, getRecommendPost } from '@/lib/utils/post'
+import { checkSlugHasNoSlash, processPostData } from '@/lib/utils/post'
import { DynamicLayout } from '@/themes/theme'
import md5 from 'js-md5'
import { useRouter } from 'next/router'
@@ -141,59 +140,12 @@ export async function getStaticProps({ params: { prefix }, locale }) {
props.post = post
}
}
- // 无法获取文章
if (!props?.post) {
+ // 无法获取文章
props.post = null
- return {
- props,
- revalidate: process.env.EXPORT
- ? undefined
- : siteConfig(
- 'NEXT_REVALIDATE_SECOND',
- BLOG.NEXT_REVALIDATE_SECOND,
- props.NOTION_CONFIG
- )
- }
- }
-
- // 文章内容加载
- if (!props?.post?.blockMap) {
- props.post.blockMap = await getPostBlocks(props.post.id, from)
- }
-
- // 目录默认加载
- if (props.post?.blockMap?.block) {
- props.post.content = Object.keys(props.post.blockMap.block).filter(
- key => props.post.blockMap.block[key]?.value?.parent_id === props.post.id
- )
- props.post.toc = getPageTableOfContents(props.post, props.post.blockMap)
- }
-
- // 生成全文索引 && process.env.npm_lifecycle_event === 'build' && JSON.parse(BLOG.ALGOLIA_RECREATE_DATA)
- if (BLOG.ALGOLIA_APP_ID) {
- uploadDataToAlgolia(props?.post)
- }
-
- // 推荐关联文章处理
- const allPosts = props.allPages?.filter(
- page => page.type === 'Post' && page.status === 'Published'
- )
- if (allPosts && allPosts.length > 0) {
- const index = allPosts.indexOf(props.post)
- props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
- props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
- props.recommendPosts = getRecommendPost(
- props.post,
- allPosts,
- siteConfig('POST_RECOMMEND_COUNT')
- )
} else {
- props.prev = null
- props.next = null
- props.recommendPosts = []
+ await processPostData(props, from)
}
-
- delete props.allPages
return {
props,
revalidate: process.env.EXPORT
diff --git a/themes/heo/components/PostHeader.js b/themes/heo/components/PostHeader.js
index e4452e92..88711b50 100644
--- a/themes/heo/components/PostHeader.js
+++ b/themes/heo/components/PostHeader.js
@@ -31,9 +31,8 @@ export default function PostHeader({ post, siteInfo, isDarkMode }) {
height: 100%;
top: 0;
left: 0;
- box-shadow: 110px -130px 500px 100px ${isDarkMode
- ? '#CA8A04'
- : '#0060e0'} inset;
+ box-shadow: 110px -130px 500px 100px
+ ${isDarkMode ? '#CA8A04' : '#0060e0'} inset;
}
`}
@@ -105,7 +104,10 @@ export default function PostHeader({ post, siteInfo, isDarkMode }) {
-
+
{post?.type !== 'Page' && (
<>
diff --git a/themes/heo/index.js b/themes/heo/index.js
index 93a7dabb..6fa85552 100644
--- a/themes/heo/index.js
+++ b/themes/heo/index.js
@@ -42,6 +42,7 @@ import SearchNav from './components/SearchNav'
import SideRight from './components/SideRight'
import CONFIG from './config'
import { Style } from './style'
+import AISummary from '@/components/AISummary'
/**
* 基础布局 采用上中下布局,移动端使用顶部侧边导航栏
@@ -306,6 +307,7 @@ const LayoutSlug = props => {
+
{post && }
diff --git a/themes/matery/components/ArticleInfo.js b/themes/matery/components/ArticleInfo.js
index af3fa823..010d5e85 100644
--- a/themes/matery/components/ArticleInfo.js
+++ b/themes/matery/components/ArticleInfo.js
@@ -1,46 +1,48 @@
import Link from 'next/link'
import { useGlobal } from '@/lib/global'
import TagItemMiddle from './TagItemMiddle'
-import WordCount from './WordCount'
import { formatDateFmt } from '@/lib/utils/formatDate'
+import WordCount from '@/components/WordCount'
-export const ArticleInfo = (props) => {
+export const ArticleInfo = props => {
const { post } = props
const { locale } = useGlobal()
return (
-
-
- {post.tagItems && (
-
- {post.tagItems.map(tag => (
-
- ))}
-
- )}
-
+
+
+ {post.tagItems && (
+
+ {post.tagItems.map(tag => (
+
+ ))}
+
+ )}
+
-
- {post?.type !== 'Page' && (<>
-
-
- {locale.COMMON.POST_TIME}: {post?.publishDay}
-
-
-
- {locale.COMMON.LAST_EDITED_TIME}: {post.lastEditedDay}
-
-
-
-
-
- >)}
-
-
-
+
+ {post?.type !== 'Page' && (
+ <>
+
+ {' '}
+ {locale.COMMON.POST_TIME}: {post?.publishDay}
+
+
+
+ {locale.COMMON.LAST_EDITED_TIME}: {post.lastEditedDay}
+
+
+
+
+
+
+ >
+ )}
+
+
)
}
diff --git a/themes/matery/components/WordCount.js b/themes/matery/components/WordCount.js
deleted file mode 100644
index d7d7c02b..00000000
--- a/themes/matery/components/WordCount.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useGlobal } from '@/lib/global'
-import { useEffect } from 'react'
-
-/**
- * 字数统计
- * @returns
- */
-export default function WordCount() {
- const { locale } = useGlobal()
- useEffect(() => {
- countWords()
- })
-
- return
-
-
- {locale.COMMON.WORD_COUNT}
- 0
-
-
-
-
- 0 {locale.COMMON.MINUTE}
-
-
-}
-
-/**
- * 更新字数统计和阅读时间
- */
-function countWords() {
- const articleText = deleteHtmlTag(document.querySelector('#article-wrapper #notion-article')?.innerHTML)
- const wordCount = fnGetCpmisWords(articleText)
- // 阅读速度 300-500每分钟
- document.getElementById('wordCount').innerHTML = wordCount
- document.getElementById('readTime').innerHTML = Math.floor(wordCount / 400) + 1
- const wordCountWrapper = document.getElementById('wordCountWrapper')
- wordCountWrapper.classList.remove('hidden')
-}
-
-// 去除html标签
-function deleteHtmlTag(str) {
- if (!str) {
- return ''
- }
- str = str.replace(/<[^>]+>|&[^>]+;/g, '').trim()// 去掉所有的html标签和 之类的特殊符合
- return str
-}
-
-// 用word方式计算正文字数
-function fnGetCpmisWords(str) {
- if (!str) {
- return 0
- }
- let sLen = 0
- try {
- // eslint-disable-next-line no-irregular-whitespace
- str = str.replace(/(\r\n+|\s+| +)/g, '龘')
- // eslint-disable-next-line no-control-regex
- str = str.replace(/[\x00-\xff]/g, 'm')
- str = str.replace(/m+/g, '*')
- str = str.replace(/龘+/g, '')
- sLen = str.length
- } catch (e) {
-
- }
- return sLen
-}
diff --git a/themes/next/components/ArticleDetail.js b/themes/next/components/ArticleDetail.js
index 61bcdb2f..902cf6a6 100644
--- a/themes/next/components/ArticleDetail.js
+++ b/themes/next/components/ArticleDetail.js
@@ -14,7 +14,7 @@ import ArticleCopyright from './ArticleCopyright'
import BlogAround from './BlogAround'
import RecommendPosts from './RecommendPosts'
import TagItem from './TagItem'
-import WordCount from './WordCount'
+import WordCount from '@/components/WordCount'
/**
*
@@ -92,7 +92,7 @@ export default function ArticleDetail(props) {
)}
-
+
)}
diff --git a/themes/next/components/WordCount.js b/themes/next/components/WordCount.js
deleted file mode 100644
index 6cfd1cc6..00000000
--- a/themes/next/components/WordCount.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useGlobal } from '@/lib/global'
-import { useEffect } from 'react'
-
-/**
- * 字数统计
- * @returns
- */
-export default function WordCount() {
- useEffect(() => {
- countWords()
- })
-
- const { locale } = useGlobal()
-
- return
-
- {locale.COMMON.WORD_COUNT}≈
- 0 | {locale.COMMON.READ_TIME} ≈ 0 {locale.COMMON.MINUTE}
-
-}
-
-/**
- * 更新字数统计和阅读时间
- */
-function countWords() {
- const articleText = deleteHtmlTag(document.querySelector('#article-wrapper #notion-article')?.innerHTML)
- const wordCount = fnGetCpmisWords(articleText)
- // 阅读速度 300-500每分钟
- document.getElementById('wordCount').innerHTML = wordCount
- document.getElementById('readTime').innerHTML = Math.floor(wordCount / 400) + 1
- const wordCountWrapper = document.getElementById('wordCountWrapper')
- wordCountWrapper.classList.remove('hidden')
-}
-
-// 去除html标签
-function deleteHtmlTag(str) {
- if (!str) {
- return ''
- }
- str = str.replace(/<[^>]+>|&[^>]+;/g, '').trim()// 去掉所有的html标签和 之类的特殊符合
- return str
-}
-
-// 用word方式计算正文字数
-function fnGetCpmisWords(str) {
- if (!str) {
- return 0
- }
- let sLen = 0
- try {
- // eslint-disable-next-line no-irregular-whitespace
- str = str.replace(/(\r\n+|\s+| +)/g, '龘')
- // eslint-disable-next-line no-control-regex
- str = str.replace(/[\x00-\xff]/g, 'm')
- str = str.replace(/m+/g, '*')
- str = str.replace(/龘+/g, '')
- sLen = str.length
- } catch (e) {
-
- }
- return sLen
-}