rhizome-node/src/utils/md-files.ts
Lentil Hoffman 5afd3232cb
feat: enable inter-plugin state sharing in CustomResolver
- Update ResolverPlugin interface to include context in update and resolve methods
- Modify CustomResolver to pass entity state and resolved values to plugins
- Update built-in plugins to accept and use the new context parameter
- Add comprehensive test for inter-plugin communication
- Add documentation for the new view composition patterns

This change enables plugins to access each other's states during both update and resolve phases, allowing for more powerful and flexible resolver compositions.
2025-06-22 20:42:05 -05:00

161 lines
4.0 KiB
TypeScript

import Debug from "debug";
import {FSWatcher, readdirSync, readFileSync, watch, accessSync, constants} from "fs";
import path, {join} from "path";
import showdown from "showdown";
import {RhizomeNode} from "../node";
const {Converter} = showdown;
const debug = Debug('rz:md-files');
const docConverter = new Converter({
completeHTMLDocument: true,
simpleLineBreaks: false,
tables: true,
tasklists: true
});
export type Markdown = string;
export type Html = string;
export const htmlDocFromMarkdown = (md: Markdown): Html => docConverter.makeHtml(md);
type mdFileInfo = {
name: string,
md: string,
html: string
};
export class MDFiles {
files = new Map<string, mdFileInfo>();
readme?: mdFileInfo;
dirWatcher?: FSWatcher;
readmeWatcher?: FSWatcher;
latestIndexHtml?: Html;
constructor(readonly rhizomeNode: RhizomeNode) {}
readFile(name: string) {
const md = readFileSync(join('./markdown', `${name}.md`)).toString();
let m = "";
// Add title and render the markdown
m += `# File: [${name}](/html/${name})\n\n---\n\n${md}`;
// Add footer with the nav menu
m += `\n\n---\n\n${this.generateIndex()}`;
const html = htmlDocFromMarkdown(m);
this.files.set(name, {name, md, html});
}
readReadme() {
let currentDir = process.cwd();
const root = path.parse(currentDir).root;
let readmePath: string | null = null;
// Traverse up the directory tree until we find README.md or hit the root
while (currentDir !== root) {
const testPath = path.join(currentDir, 'README.md');
try {
// Using the imported accessSync function
accessSync(testPath, constants.F_OK);
readmePath = testPath;
break;
} catch (err) {
// Move up one directory
currentDir = path.dirname(currentDir);
}
}
if (!readmePath) {
debug('No README.md found in any parent directory');
return;
}
const md = readFileSync(readmePath).toString();
const html = htmlDocFromMarkdown(md);
this.readme = { name: 'README', md, html };
}
getReadmeHTML() {
return this.readme?.html;
}
getHtml(name: string): string | undefined {
return this.files.get(name)?.html;
}
list(): string[] {
return Array.from(this.files.keys());
}
generateIndex(): Markdown {
let md = `# [Index](/html)\n\n`;
md += `[README](/html/README)\n\n`;
for (const name of this.list()) {
md += `- [${name}](/html/${name})\n`;
}
return htmlDocFromMarkdown(md);
}
get indexHtml(): Html {
if (!this.latestIndexHtml) {
this.latestIndexHtml = this.generateIndex();
}
return this.latestIndexHtml;
}
readDir() {
// Read list of markdown files from directory and
// render each markdown file as html
readdirSync('./markdown/')
.filter((f) => f.endsWith('.md'))
.map((name) => path.parse(name).name)
.forEach((name) => this.readFile(name));
}
watchDir() {
this.dirWatcher = watch('./markdown', null, (eventType, filename) => {
if (!filename) return;
if (!filename.endsWith(".md")) return;
const name = path.parse(filename).name;
switch (eventType) {
case 'rename': {
debug(`[${this.rhizomeNode.config.peerId}]`, `File ${name} renamed`);
// Remove it from memory and re-scan everything
this.files.delete(name);
this.readDir();
break;
}
case 'change': {
debug(`[${this.rhizomeNode.config.peerId}]`, `File ${name} changed`);
// Re-read this file
this.readFile(name)
break;
}
}
});
}
watchReadme() {
this.readmeWatcher = watch('./README.md', null, (eventType, filename) => {
if (!filename) return;
switch (eventType) {
case 'change': {
debug(`[${this.rhizomeNode.config.peerId}]`, `README file changed`);
// Re-read this file
this.readReadme()
break;
}
}
});
}
stop() {
this.dirWatcher?.close();
this.readmeWatcher?.close();
}
}