@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
160 lines • 5.99 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContentFileWatcher = void 0;
const chokidar = __importStar(require("chokidar"));
const path = __importStar(require("path"));
const log_1 = require("../../log");
const log = (0, log_1.getDomainLogger)('web', { component: 'content-file-watcher' });
class ContentFileWatcher {
constructor(config) {
this.config = config;
this.watcher = null;
this.eventQueue = [];
this.batchTimer = null;
this.callbacks = [];
this.batchDelay = config.batchDelay || 500; // 与 Golang 版本保持一致的 500ms
}
async startWatching() {
if (this.watcher) {
await this.stopWatching();
}
this.watcher = chokidar.watch(this.config.contentDir, {
ignored: [
/(^|[\/\\])\../, // 隐藏文件
/\.tmp$/, // 临时文件
/\.swp$/, // vim 临时文件
/~$/, // 备份文件
/\.DS_Store$/, // macOS 文件
...(this.config.ignorePatterns || [])
],
persistent: true,
ignoreInitial: true,
followSymlinks: true,
awaitWriteFinish: {
stabilityThreshold: 100,
pollInterval: 50
}
});
this.watcher
.on('add', (filePath) => this.queueEvent(filePath, 'created'))
.on('change', (filePath) => this.queueEvent(filePath, 'modified'))
.on('unlink', (filePath) => this.queueEvent(filePath, 'deleted'))
.on('error', (error) => {
log.error('File watcher error:', error);
});
}
onFileChange(callback) {
this.callbacks.push(callback);
}
queueEvent(filePath, eventType) {
let fp = filePath;
const { projContentDir, contentDir } = this.config;
if (projContentDir !== contentDir && fp.startsWith(contentDir)) {
fp = projContentDir + fp.slice(contentDir.length);
}
const normalizedPath = path.normalize(fp);
// 只监控 .md 文件和图片文件
if (!this.isRelevantFile(normalizedPath)) {
return;
}
const event = {
filePath: normalizedPath,
eventType,
timestamp: Date.now(),
isMarkdown: this.isMarkdownFile(normalizedPath),
isImage: this.isImageFile(normalizedPath)
};
this.eventQueue.push(event);
this.scheduleBatch();
}
isRelevantFile(filePath) {
return this.isMarkdownFile(filePath) || this.isImageFile(filePath);
}
isMarkdownFile(filePath) {
const ext = path.extname(filePath).toLowerCase();
return ext === '.md' || ext === '.markdown';
}
isImageFile(filePath) {
const ext = path.extname(filePath).toLowerCase();
return ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.bmp'].includes(ext);
}
scheduleBatch() {
if (this.batchTimer) {
clearTimeout(this.batchTimer);
}
// 与 Golang 版本保持一致的 500ms 批处理延迟
this.batchTimer = setTimeout(async () => {
if (this.eventQueue.length > 0) {
const events = this.deduplicateEvents(this.eventQueue);
this.eventQueue = [];
// 通知所有回调
for (const callback of this.callbacks) {
try {
await callback(events);
}
catch (error) {
log.error('Error processing file change events:', error);
}
}
}
}, this.batchDelay);
}
deduplicateEvents(events) {
const eventMap = new Map();
// 保留每个文件的最新事件,与 Golang 版本逻辑一致
for (const event of events) {
const existing = eventMap.get(event.filePath);
if (!existing || event.timestamp > existing.timestamp) {
eventMap.set(event.filePath, event);
}
}
return Array.from(eventMap.values());
}
async stopWatching() {
if (this.batchTimer) {
clearTimeout(this.batchTimer);
this.batchTimer = null;
}
if (this.watcher) {
await this.watcher.close();
this.watcher = null;
}
this.eventQueue = [];
this.callbacks = [];
}
}
exports.ContentFileWatcher = ContentFileWatcher;
//# sourceMappingURL=content-file-watcher.js.map