Initial commit
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import type { ChapterView } from '../types.ts';
|
||||
|
||||
const TEMPLATE_PATH = path.resolve(process.cwd(), 'templates', 'book.template.html');
|
||||
|
||||
let templateCache: string | null = null;
|
||||
|
||||
function getTemplate(): string {
|
||||
if (!templateCache) templateCache = readFileSync(TEMPLATE_PATH, 'utf-8');
|
||||
return templateCache;
|
||||
}
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function readChapterLines(filePath: string): string[] {
|
||||
try {
|
||||
const raw = readFileSync(filePath, 'utf-8');
|
||||
// Both plain-text and JSON chapters are read as plain text for HTML output,
|
||||
// matching the C# SomeBasicFileView.GetAsHTML() behaviour.
|
||||
try {
|
||||
const fragments = JSON.parse(raw) as Array<{ body: string }>;
|
||||
if (Array.isArray(fragments) && fragments.length > 0 && 'body' in (fragments[0] ?? {})) {
|
||||
return fragments.map(f => f.body).filter(Boolean);
|
||||
}
|
||||
} catch {
|
||||
// not JSON — fall through to plain text
|
||||
}
|
||||
return raw.split(/[\n\r]+/).map(l => l.trim()).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function renderHtml(view: ChapterView): string {
|
||||
const template = getTemplate();
|
||||
|
||||
const content = view.chapters
|
||||
.map((chapter, i) => {
|
||||
const chapNum = view.min + i;
|
||||
const lines = readChapterLines(chapter.path);
|
||||
if (lines.length === 0) return '';
|
||||
const paragraphs = lines
|
||||
.map((line, j) => ` <p id="para${chapNum}-${j}">${escapeHtml(line)}</p>`)
|
||||
.join('\n');
|
||||
return `<div id="chap${chapNum}">\n${paragraphs}\n</div>`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
|
||||
return template
|
||||
.replace('{id}', String(view.id))
|
||||
.replace('{min}', String(view.min))
|
||||
.replace('{max}', String(view.max))
|
||||
.replace('{content}', content);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import type { ChapterView, TextFragment } from '../types.ts';
|
||||
|
||||
interface ChapterRow {
|
||||
chapter: number;
|
||||
contents: string[] | TextFragment[];
|
||||
}
|
||||
|
||||
function readChapterContents(filePath: string, hasStyle: boolean): string[] | TextFragment[] {
|
||||
try {
|
||||
const raw = readFileSync(filePath, 'utf-8');
|
||||
if (hasStyle) {
|
||||
return JSON.parse(raw) as TextFragment[];
|
||||
}
|
||||
return raw.split(/[\n\r]+/).map(l => l.trim()).filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function renderJson(view: ChapterView): string {
|
||||
const rows: ChapterRow[] = view.chapters.map((chapter, i) => ({
|
||||
chapter: view.min + i,
|
||||
contents: readChapterContents(chapter.path, chapter.hasStyle),
|
||||
}));
|
||||
return JSON.stringify(rows);
|
||||
}
|
||||
Reference in New Issue
Block a user