import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { remark } from 'remark'
import html from 'remark-html'
import remarkPrism from "remark-prism";
import Info from '@/components/Info'

export const info = Info();
export const Directory = path.join(process.cwd(), info.path)

export function getAllPostIds() {
  const subdirectories = fs.readdirSync(Directory, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory())
    .map(dirent => dirent.name)

  const posts = subdirectories.flatMap(subdirectory => {
    const directoryPath = path.join(Directory, subdirectory)
    const filenames = fs.readdirSync(directoryPath)
      .filter(filename => filename.endsWith('.md'))

    return filenames.map(filename => {
      return {
        params: {
          slug: [subdirectory, filename.replace(/\.md$/, '.html')],
        },
      }
    })
  })

  // Sort posts by date
  // Format the path correctly
  const paths = posts.map(({ params }) => ({ params: { slug: params.slug } }));

  return paths;
}

export async function getSortedPostsData() {

  const subdirectories = fs.readdirSync(Directory, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory())
    .map(dirent => dirent.name)

  const posts = (await Promise.all(subdirectories.map(async (subdirectory) => {
    const directoryPath = path.join(Directory, subdirectory)
    const filenames = fs.readdirSync(directoryPath)
      .filter(filename => filename.endsWith('.md'))

      const allPostsData = await Promise.all(filenames.map(async (filename) => {
        const slug = [subdirectory, filename.replace(/\.md$/, '').concat('.html')].join('/')
        const fullPath = path.join(Directory, [subdirectory, filename].join('//'))
        const fileContents = fs.readFileSync(fullPath, 'utf8')
        const matterResult = matter(fileContents)
        const contentCount = await remark()
          .use(html, { sanitize: false })
          .process(matterResult.content)

        const contCount = contentCount.toString().replace(/<\/?(h\d).*>/g, '');
        const NoImages = contCount.replace(/<img[^>]*>/g, '').trim();
        const NoHtml = NoImages.replace(/<\/?[^>]+(>|$)/g, '');
        const cnMatches = NoHtml.match(/[一-龥]/g) || [];
        const enMatches = NoHtml.match(/[a-zA-Z]+/g) || [];  
        const cnCount = cnMatches.length;
        const enCount = enMatches.length;  
        const wordCount = cnCount + enCount - info.exclude;
    
        const postData = {
            slug,
            wordCount,
            ...(matterResult.data as { date: string; title: string })
        };
        return postData;
    }))
    const sortedPostsData = allPostsData.reduce((acc, elems) => {
        if(Array.isArray(elems)) {
            return acc.concat(elems);
        }
        acc.push(elems);
        return acc;
    }, []);
    return sortedPostsData.sort((a, b) => {
        if (a.date < b.date) {
            return 1
        } else {
            return -1
        }
    })
  }))).flat()

  return posts
}

export async function getPostData(slug:any) {

  const idjoin = slug.toString().split(',').join('//').replace(/\.html$/, '')
  const id = path.join(idjoin);
  const fullPath = path.join(Directory, `${id}.md`)
  const fileContents = fs.readFileSync(fullPath, 'utf8')
  const matterResult = matter(fileContents)

  const processedContent = await remark()
    .use(html, { sanitize: false })
    .use(remarkPrism, { plugins: ['line-numbers'] })
    .process(matterResult.content)
  const contentHtml = processedContent.toString()

  const contentCount = await remark()
    .use(html, { sanitize: false })
    .process(matterResult.content)

  const contCount = contentCount.toString().replace(/<\/?(h\d).*>/g, '');
  const NoImages = contCount.replace(/<img[^>]*>/g, '').trim();
  const NoHtml = NoImages.replace(/<\/?[^>]+(>|$)/g, '');
  const cnMatches = NoHtml.match(/[一-龥]/g) || [];
  const enMatches = NoHtml.match(/[a-zA-Z]+/g) || [];  
  const cnCount = cnMatches.length;
  const enCount = enMatches.length;  
  const wordCount = cnCount + enCount - info.exclude;
  console.log(`文章页面字数为:${wordCount}`);
  
  const baseUrl = info.baseURL;
  const imgRegex = /<img.*?src="(.*?)"/g;
  let match: any[];
  const imgUrlArr = [];
  while ((match = imgRegex.exec(contentHtml)) !== null) {
    const imgPath = match[1];
    const imgUrl = `${baseUrl}${imgPath}`;
    imgUrlArr.push(imgUrl);
  }
  const filePath = fullPath;
  const fileStat = fs.statSync(filePath);
  const mtime = fileStat.mtime.toISOString();
  const birthtime = fileStat.birthtime.toISOString();
  console.log(birthtime);

  const summary = extractSummary(NoImages, 80);
  const desc = summary.replace(/<\/?[^>]+(>|$)/g, '');

  return {
    birthtime,
    mtime,
    slug,
    wordCount,
    imgUrlArr,
    summary,
    desc,
    contentHtml,
    ...(matterResult.data as { date: string; title: string; tags: string })
  }

}

function extractSummary(content: string, minLength: number): string {
  const filtered = content.replace(/<\/?(h\d).*>/g, '');
  
  const paragraphs = filtered.split(/[

]+/);
  let summary = '';
  for (let i = 0; i < paragraphs.length; i++) {
    const newSummaryLength = summary.length + paragraphs[i].length;
    if (newSummaryLength < minLength) {
      summary += paragraphs[i];
    } else {

      let j = i;
      while (j < paragraphs.length && summary.length < minLength) {
        summary += paragraphs[j];
        j++;
      }
      break;
    }
  }

  return summary.trim();
}
/lib/blog.ts

使用 Next.js 13 怎么把 /lib/blog.ts 里的数据输出到 /components/InfoTotal.tsx
import { SiteRuntime } from './SiteRuntime';

export default function InfoTotal() {

    return (
        <section className='info'>
            <div className='info-fanma'>
            <h2>导航信息</h2>
                <ul>
                    <li><span className='l'></span><span className='r'></span></li>
                    <li><span className='l'>收录网站:</span><span className='r'></span></li>
                    <SiteRuntime />
                </ul>
            </div>
        </section>
    )
}

/components/InfoTotal.tsx内容:import { getAllPostIds, getSortedPostsData, Directory } from '../lib/blog'

export function SiteRuntime() {
  const posts = getSortedPostsData();
  const subdirectories = posts.map((post) => post.slug.split('/')[0]);
  const uniqueSubdirectories = [...new Set(subdirectories)];
  const postCount = posts.length;
  const categoryCount = uniqueSubdirectories.length;

  const stats = {
    '文章总数': postCount,
    '分类总数': categoryCount,
  };

  return (
    <>
      {Object.entries(stats).map(([key, value], index) => (
        <li key={index}>
          <span className='l'>{key}:</span>
          <span className='r'>{value}</span>
        </li>
      ))}
      <li>
        <span className='l'>最近更新:</span>
        <span className='r'>{getLastUpdateTime(Directory)}</span>
      </li>
    </>
  );
}

export function getLastUpdateTime(directory: string): string {
  const subdirectories = fs.readdirSync(directory, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory())
    .map(dirent => dirent.name)

  const lastModified = Math.max(
    ...subdirectories.map((subdirectory) => {
      const directoryPath = path.join(directory, subdirectory)
      const filenames = fs.readdirSync(directoryPath)
        .filter(filename => filename.endsWith('.md'))
        .map(filename => path.join(Directory, [subdirectory, filename].join('//')))
      const stats = filenames.map(filename => fs.statSync(filename))
      const lastModified = Math.max(...stats.map(stat => stat.mtimeMs))
      return lastModified
    })
  )

  return new Date(lastModified).toLocaleDateString();
}

代码解读

代码实现了一个简单的博客文章数据获取和统计功能。

  1. lib/blog.ts: 定义了三个函数,用于获取所有文章的 ID、获取所有文章的数据和获取单个文章的数据。
    • getAllPostIds(): 获取所有文章的 ID,并以 params.slug 的形式存储。
    • getSortedPostsData(): 获取所有文章的数据,包括文章标题、日期、字数等信息,并按照日期排序。
    • getPostData(slug: any): 获取单个文章的数据,包括文章内容、图片路径、摘要、字数等信息。
  2. components/InfoTotal.tsx: 使用 lib/blog.ts 中定义的函数获取文章数据,并统计文章总数、分类总数和最近更新时间。
    • SiteRuntime(): 使用 getSortedPostsData() 获取所有文章数据,并统计文章总数、分类总数和最近更新时间。
    • getLastUpdateTime(directory: string): 获取指定目录下的所有文件,并返回最近修改的时间。

总结

代码使用 Next.js 13 的特性,实现了博客文章数据的获取和统计功能,展示了如何在 Next.js 中处理静态数据和动态数据。

代码优化

  • 为了提高代码的可读性和可维护性,可以将代码拆分成多个文件,例如将 getAllPostIds()getSortedPostsData() 函数分别放在 lib/posts.tslib/post.ts 文件中。
  • 可以使用 data-fetching 的方式,将数据预先加载到页面中,以提高页面加载速度。
  • 可以使用 SWRReact Query 等库来管理数据获取和缓存,以简化代码逻辑和提高代码效率。

其他

  • 可以根据需要添加其他统计指标,例如文章阅读量、评论数等。
  • 可以使用第三方库来生成文章摘要,以提供更好的用户体验。
  • 可以使用 Next.js 的路由系统来创建博客页面,并实现文章列表、文章详情、分类页面等功能。

使用示例

// 在页面组件中使用 getSortedPostsData() 获取文章数据
import { getSortedPostsData } from '../lib/blog';

export default function Home() {
  const posts = getSortedPostsData();
  
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.slug}>
          <a href={`/posts/${post.slug}`}>{post.title}</a>
        </li>
      ))}
    </ul>
  );
}
// 在页面组件中使用 getPostData(slug) 获取单个文章数据
import { getPostData } from '../lib/blog';

export async function getStaticProps({ params }) {
  const postData = await getPostData(params.slug);
  
  return {
    props: { postData },
  };
}

export default function Post({ postData }) {
  
  return (
    <div>
      <h1>{postData.title}</h1>
      <p>{postData.contentHtml}</p>
    </div>
  );
}
Next.js 13 博客文章数据获取与统计

原文地址: https://www.cveoy.top/t/topic/mYif 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录