Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | 1x 1x 22x 22x 22x 11x 11x 11x 10x 10x 1x 1x 12x 12x 12x 1x 32x 32x 32x 32x 32x 32x 32x 32x 32x 9x 9x 9x 9x 9x 8x 8x 8x 6x 6x 6x 6x 8x 9x 9x 9x 32x 3x 3x 3x 3x 3x 32x 14x 14x 14x 28x 28x 28x 24x 28x 8x 6x 5x 8x 4x 4x 4x 8x 24x 14x 14x 32x 3x 3x 32x 8x 8x 4x 8x 3x 8x 2x 8x 32x 12x 36x 36x 6x 6x 36x 12x 32x | import type { DataAdapter } from "obsidian";
import type { WikiPage, WikiPageDetail } from "./types";
import { PUBLISHED_WIKI_PAGE_TYPES } from "./types";
import type { LilbeeClient } from "./api";
const MANAGED_MARKER = "lilbee_managed";
function pageVaultPath(folder: string, page: WikiPage): string {
// The slug already includes the subdir (e.g. "summaries/lilbee for Obsidian")
return `${folder}/${page.slug}.md`;
}
function buildFileContent(page: WikiPageDetail): string {
// The server's content already includes YAML frontmatter (generated_by, sources, etc.).
// Inject the managed marker into the frontmatter so Obsidian parses it correctly.
// Obsidian requires `---` as the very first line to recognize frontmatter.
const content = page.content;
if (content.startsWith("---\n")) {
return content.replace("---\n", `---\n${MANAGED_MARKER}: true\n`);
}
// No frontmatter — wrap content with frontmatter containing only the marker
return `---\n${MANAGED_MARKER}: true\n---\n\n${content}`;
}
function isManagedFile(content: string): boolean {
return content.includes(MANAGED_MARKER);
}
export class WikiSync {
private api: LilbeeClient;
private vault: DataAdapter;
private folder: string;
constructor(api: LilbeeClient, vault: DataAdapter, folder: string) {
this.api = api;
this.vault = vault;
this.folder = folder;
}
async reconcile(): Promise<{ written: number; removed: number }> {
const pages = await this.api.wikiList();
const publishedPages = pages.filter((p) => PUBLISHED_WIKI_PAGE_TYPES.has(p.page_type));
await this.ensureFolders();
let written = 0;
for (const page of publishedPages) {
const path = pageVaultPath(this.folder, page);
const needsWrite = await this.needsUpdate(path, page);
if (needsWrite) {
const detail = await this.api.wikiPage(page.slug);
await this.vault.write(path, buildFileContent(detail));
written++;
}
}
const removed = await this.removeStalePages(publishedPages);
return { written, removed };
}
async writePage(slug: string): Promise<void> {
const detail = await this.api.wikiPage(slug);
const path = pageVaultPath(this.folder, detail);
await this.ensureFolders();
await this.vault.write(path, buildFileContent(detail));
}
async removeStalePages(currentPages: WikiPage[]): Promise<number> {
const currentPaths = new Set(currentPages.map((p) => pageVaultPath(this.folder, p)));
let removed = 0;
for (const subdir of ["summaries", "concepts"]) {
const dirPath = `${this.folder}/${subdir}`;
const dirExists = await this.vault.exists(dirPath);
if (!dirExists) continue;
const listing = await this.vault.list(dirPath);
for (const filePath of listing.files) {
if (!filePath.endsWith(".md")) continue;
if (currentPaths.has(filePath)) continue;
const content = await this.vault.read(filePath);
if (isManagedFile(content)) {
await this.vault.remove(filePath);
removed++;
}
}
}
return removed;
}
isWikiPath(path: string): boolean {
return path.startsWith(this.folder + "/");
}
private async needsUpdate(path: string, page: WikiPage): Promise<boolean> {
const exists = await this.vault.exists(path);
if (!exists) return true;
const content = await this.vault.read(path);
if (!isManagedFile(content)) return false;
const match = content.match(/generated_at:\s*(.+)/);
if (!match) return true;
return page.created_at === null || match[1].trim() !== page.created_at;
}
private async ensureFolders(): Promise<void> {
for (const dir of [this.folder, `${this.folder}/summaries`, `${this.folder}/concepts`]) {
const exists = await this.vault.exists(dir);
if (!exists) {
await this.vault.mkdir(dir);
}
}
}
}
export { pageVaultPath, buildFileContent, isManagedFile, MANAGED_MARKER };
|