- Editorial web platform built with Node.js, Express, EJS, and Vanilla CSS - Markdown content engine with YAML frontmatter, bilingual variants, and hot-reload watcher - Unified SQLite with FTS5 search indexing, query logging, and schema filters - Centered editorial post layout with margin-docked left TOC and mobile bottom sheet - WordPress-grade comment system with moderation dashboard and bulk operations under /guanli - Complete SEO suite (JSON-LD structured data, RSS/Atom feeds, XML sitemap, robots.txt) - CI/CD workflows for linting, security audits (Gitleaks), automated tests, and Docker releases
61 lines
1.8 KiB
JavaScript
61 lines
1.8 KiB
JavaScript
const chokidar = require('chokidar');
|
|
const path = require('path');
|
|
const contentService = require('./content');
|
|
const pluginManager = require('./plugin-manager');
|
|
const { loadSiteConfig } = require('../config');
|
|
|
|
class WatcherService {
|
|
constructor() {
|
|
this.watcher = null;
|
|
this.debounceTimer = null;
|
|
}
|
|
|
|
start(contentDir, app) {
|
|
console.log(`[Watcher] Starting filesystem watcher on ${contentDir}`);
|
|
|
|
this.watcher = chokidar.watch(contentDir, {
|
|
ignored: [
|
|
/(^|[\/\\])\../, // dotfiles
|
|
/(^|[\/\\])data([\/\\]|$)/, // ignore entire data/ directory (site.db, journals, etc.)
|
|
/\.db(-journal|-wal|-shm)?$/, // sqlite database files
|
|
/node_modules/
|
|
],
|
|
persistent: true,
|
|
ignoreInitial: true,
|
|
awaitWriteFinish: {
|
|
stabilityThreshold: 300,
|
|
pollInterval: 100
|
|
}
|
|
});
|
|
|
|
const triggerReload = (event, filePath) => {
|
|
const relative = path.relative(contentDir, filePath);
|
|
console.log(`[Watcher] Detected ${event} on ${relative}`);
|
|
|
|
clearTimeout(this.debounceTimer);
|
|
this.debounceTimer = setTimeout(() => {
|
|
try {
|
|
const freshConfig = loadSiteConfig();
|
|
pluginManager.loadPlugins(contentDir, app, freshConfig);
|
|
contentService.reload();
|
|
console.log(`[Watcher] Hot reload finished successfully.`);
|
|
} catch (err) {
|
|
console.error(`[Watcher] Error during hot reload:`, err);
|
|
}
|
|
}, 250);
|
|
};
|
|
|
|
this.watcher.on('add', (p) => triggerReload('add', p));
|
|
this.watcher.on('change', (p) => triggerReload('change', p));
|
|
this.watcher.on('unlink', (p) => triggerReload('unlink', p));
|
|
}
|
|
|
|
close() {
|
|
if (this.watcher) {
|
|
this.watcher.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new WatcherService();
|