Skip to content

制作文章图片集合

August 10, 2024 by ccforeverd

使用 Vite 插件, 为文档工具制作图片集合

需求整理

  • 之前在写文档时, 会把收集的图片选一张放在文档结尾, 会遇到一个问题: 图片可能会重复
  • 想把写好的文档, 配合结尾图片, 做一个瀑布流展示

获取图片数据

在每次开发和打包时, 会自动获取图片, 并生成一个图片集合

选择 configResolved 钩子, 在 vite.config.ts 中添加如下代码

同时, 做图片查重

vite.config.ts
      {
        name: 'ccforeverd:img-urls-gen',
        async configResolved() {
          logger.info('start plugin img-urls-gen');
 
          imgUrlsGen(
            ['./docs/pages/**/*.mdx'],
            './docs/generated/imgUrls.json',
          );
 
          logger.info('end plugin img-urls-gen');
        },
      },

具体代码如下

imgUrlsGen.ts
import fs from 'fs-extra';
import { globbySync } from 'globby';
import { LinesAndColumns, type SourceLocation } from 'lines-and-columns';
import { type HeadProps, getMdHead } from './getMdHead';
 
const urlReg =
  /(https?|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/;
const componentName = 'EndOfFile';
const propName = 'imgUrl';
 
interface DataItem extends HeadProps {
  url: string;
  location: SourceLocation;
  filePath: string;
}
 
export const imgUrlsGen = (glob: string[], output: string) => {
  const files = globbySync(glob);
  const images = new Set<string>(); // 用来去重
  const data: DataItem[] = [];
 
  for (const filePath of files) {
    const content = fs.readFileSync(filePath, 'utf-8');
    const componentNameIndex = content.indexOf(componentName);
 
    // 如果不包含 componentName,直接返回
    if (componentNameIndex === -1) {
      continue;
    }
 
    const propNameIndex = content.indexOf(propName);
 
    // 如果不包含 propName,直接返回
    if (propNameIndex === -1) {
      continue;
    }
 
    const start = content.indexOf('="', propNameIndex);
 
    // 如果不是 imgSrc=" 开头的,直接返回
    if (start - propNameIndex - propName.length !== 0) {
      continue;
    }
 
    const end = content.indexOf('"', start + 2);
    const imgUrl = content.slice(start + 2, end);
    const location = new LinesAndColumns(content).locationForIndex(start);
 
    // 如果不是一个 url,直接返回
    if (!urlReg.test(imgUrl)) {
      continue;
    }
 
    // 重复的图片
    if (images.has(imgUrl)) {
      console.warn(
        `Duplicated image url: ${imgUrl} in ${filePath}:${location.line + 1}:${location.column + 1}`,
      );
      continue;
    }
 
    const head = getMdHead<HeadProps>(content);
 
    // 是一个正经的 md 文件
    if (head?.title) {
      images.add(imgUrl);
 
      data.push({
        url: imgUrl,
        location,
        filePath,
        title: head.title,
        description: head.description || '',
        date: head.date || '',
      });
    }
  }
 
  fs.writeJSONSync(
    output,
    data.sort(
      (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
    ),
    { spaces: 2 },
  );
};

展示图片集合

使用 column 布局, 做一个简单的瀑布流, 展示图片集合

ImgGrid.tsx
import { Box, Card, Divider, Image, Stack, Text, Title } from '@mantine/core';
import { PhotoProvider, PhotoView } from 'react-photo-view';
import imgUrls from '../generated/imgUrls.json';
 
export const ImgGrid = () => {
  return (
    <Box className=" columns-3 gap-4">
      <PhotoProvider>
        {imgUrls.map((item) => (
          <Card key={item.url} className=" mb-4 shadow-xl">
            <Card.Section>
              <PhotoView src={item.url}>
                <Image src={item.url} alt={item.title} />
              </PhotoView>
            </Card.Section>
 
            <Stack
              component="a"
              // @ts-ignore
              href={item.filePath
                .replace('./docs/pages', '')
                .replace('.mdx', '')}
              className=" gap-2 mt-4"
            >
              <Title order={4}>{item.title}</Title>
 
              <Divider />
 
              <Text className=" text-base text-gray-500">
                {item.description}
              </Text>
 
              <Text className=" text-xs text-gray-700">{item.date}</Text>
            </Stack>
          </Card>
        ))}
      </PhotoProvider>
    </Box>
  );
};

效果还可以, 可以做加载优化, 并加上一些动画效果, 使得图片集合更加生动