update at 2025-10-16 16:10:58
This commit is contained in:
150
src/core/gallery-processor.ts
Normal file
150
src/core/gallery-processor.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 文件:gallery-processor.ts
|
||||
* 作用:处理文章中的图库短代码
|
||||
*
|
||||
* 职责:
|
||||
* 1. 解析和转换 gallery 短代码
|
||||
* 2. 处理本地图片目录扫描
|
||||
* 3. 生成 wikilink 格式的图片引用
|
||||
*/
|
||||
|
||||
import { stat, readdir } from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { NMPSettings } from '../settings';
|
||||
|
||||
// gallery 配置迁移到 NMPSettings(galleryPrePath, galleryNumPic)
|
||||
// 匹配示例:{{<gallery dir="/img/guanzhan/1" figcaption="毕业展" mppickall=1/>}}{{<load-photoswipe>}}
|
||||
// 支持可选 figcaption 以及 mppickall=1/0(无引号数字或布尔),若 mppickall=1 则选取目录内全部图片
|
||||
const GALLERY_SHORTCODE_REGEX = /{{<gallery\s+dir="([^"]+)"(?:\s+figcaption="([^"]*)")?(?:\s+mppickall=(?:"(1|0)"|'(1|0)'|(1|0)))?\s*\/?>}}\s*{{<load-photoswipe>}}/g;
|
||||
|
||||
// 块级 gallery:
|
||||
// {{<gallery>}}\n{{<figure src="/img/a.png" caption=".." >}}\n...\n{{</gallery>}}
|
||||
// 需要提取所有 figure 的 src basename 生成多行 wikilink
|
||||
const GALLERY_BLOCK_REGEX = /{{<gallery>}}([\s\S]*?){{<\/gallery>}}/g;
|
||||
|
||||
// figure 支持 src 或 link 属性,两者取其一
|
||||
const FIGURE_IN_GALLERY_REGEX = /{{<figure\s+(?:src|link)="([^"]+)"[^>]*>}}/g;
|
||||
|
||||
/**
|
||||
* 列出本地图片目录中的图片文件
|
||||
*/
|
||||
async function listLocalImages(dirAbs: string): Promise<string[]> {
|
||||
try {
|
||||
const stats = await stat(dirAbs);
|
||||
if (!stats.isDirectory()) return [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const files = await readdir(dirAbs);
|
||||
return files.filter(f => /(png|jpe?g|gif|bmp|webp|svg)$/i.test(f)).sort();
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从图片列表中选择指定数量的图片
|
||||
*/
|
||||
function pickImages(all: string[], limit: number): string[] {
|
||||
if (all.length <= limit) return all;
|
||||
|
||||
// 均匀采样
|
||||
const step = all.length / limit;
|
||||
const picked: string[] = [];
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const index = Math.floor(i * step);
|
||||
picked.push(all[index]);
|
||||
}
|
||||
return picked;
|
||||
}
|
||||
|
||||
/**
|
||||
* 图库处理器类
|
||||
*/
|
||||
export class GalleryProcessor {
|
||||
private settings: NMPSettings;
|
||||
|
||||
constructor(settings: NMPSettings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文章中的图库短代码
|
||||
*/
|
||||
async processGalleryShortcodes(content: string): Promise<string> {
|
||||
// 处理目录式 gallery
|
||||
content = await this.processDirectoryGalleries(content);
|
||||
|
||||
// 处理块级 gallery
|
||||
content = await this.processBlockGalleries(content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理目录式图库短代码
|
||||
*/
|
||||
private async processDirectoryGalleries(content: string): Promise<string> {
|
||||
const matches = Array.from(content.matchAll(GALLERY_SHORTCODE_REGEX));
|
||||
|
||||
for (const match of matches) {
|
||||
const [fullMatch, dir, figcaption = '', pickall1, pickall2, pickall3] = match;
|
||||
const pickall = pickall1 || pickall2 || pickall3;
|
||||
const shouldPickAll = pickall === '1';
|
||||
|
||||
try {
|
||||
const galleryPrePath = this.settings.galleryPrePath;
|
||||
const dirAbs = path.join(galleryPrePath, dir);
|
||||
const allImages = await listLocalImages(dirAbs);
|
||||
|
||||
if (allImages.length === 0) {
|
||||
console.warn(`[GalleryProcessor] 目录 ${dirAbs} 中没有找到图片`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const selectedImages = shouldPickAll
|
||||
? allImages
|
||||
: pickImages(allImages, this.settings.galleryNumPic);
|
||||
|
||||
// 生成 wikilink 格式的图片引用
|
||||
const wikilinks = selectedImages.map(img => {
|
||||
const imgPath = path.join(dir, img).replace(/\\/g, '/');
|
||||
return `![[${imgPath}]]`;
|
||||
});
|
||||
|
||||
let replacement = wikilinks.join('\n');
|
||||
if (figcaption) {
|
||||
replacement = `> ${figcaption}\n\n${replacement}`;
|
||||
}
|
||||
|
||||
content = content.replace(fullMatch, replacement);
|
||||
} catch (error) {
|
||||
console.error(`[GalleryProcessor] 处理图库失败: ${dir}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理块级图库短代码
|
||||
*/
|
||||
private processBlockGalleries(content: string): Promise<string> {
|
||||
return Promise.resolve(content.replace(GALLERY_BLOCK_REGEX, (match, blockContent) => {
|
||||
const figureMatches = Array.from(blockContent.matchAll(FIGURE_IN_GALLERY_REGEX));
|
||||
|
||||
if (figureMatches.length === 0) {
|
||||
return match; // 保持原样
|
||||
}
|
||||
|
||||
const wikilinks = figureMatches.map(([, src]) => {
|
||||
const basename = path.basename(src);
|
||||
return `![[${basename}]]`;
|
||||
});
|
||||
|
||||
return wikilinks.join('\n');
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user