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 -}