import { App, TFile, TFolder, normalizePath } from "obsidian"; import { PROJECTS_ROOT, PROJECT_FILES, TO_UPDATE_DIR } from "./const"; export type Zone = "ready" | "to-update"; export function projectsPath(): string { return PROJECTS_ROOT; } export function projectPath(name: string): string { return normalizePath(`${PROJECTS_ROOT}/${name}`); } export function toUpdatePath(project: string): string { return normalizePath(`${PROJECTS_ROOT}/${project}/${TO_UPDATE_DIR}`); } export function zoneRootPath(project: string, zone: Zone): string { return zone === "ready" ? projectPath(project) : toUpdatePath(project); } export function areaPath(project: string, area: string, zone: Zone = "ready"): string { return normalizePath(`${zoneRootPath(project, zone)}/${area}`); } export function featurePath( project: string, area: string, feature: string, zone: Zone = "ready", ): string { const file = feature.endsWith(".md") ? feature : `${feature}.md`; return normalizePath(`${zoneRootPath(project, zone)}/${area}/${file}`); } export async function ensureFolder(app: App, path: string): Promise { const p = normalizePath(path); const exists = app.vault.getAbstractFileByPath(p); if (!exists) { await app.vault.createFolder(p); } } export async function ensureFile(app: App, path: string, content = ""): Promise { const p = normalizePath(path); const existing = app.vault.getAbstractFileByPath(p); if (existing instanceof TFile) return existing; return await app.vault.create(p, content); } export function listFolders(app: App, path: string): TFolder[] { const p = normalizePath(path); const folder = app.vault.getAbstractFileByPath(p); if (!(folder instanceof TFolder)) return []; return folder.children .filter((c): c is TFolder => c instanceof TFolder) .sort((a, b) => a.name.localeCompare(b.name)); } export function listMarkdownFiles(app: App, path: string, exclude: string[] = []): TFile[] { const p = normalizePath(path); const folder = app.vault.getAbstractFileByPath(p); if (!(folder instanceof TFolder)) return []; return folder.children .filter((c): c is TFile => c instanceof TFile && c.extension === "md") .filter((f) => !exclude.includes(f.name)) .sort((a, b) => a.basename.localeCompare(b.basename)); } export async function readFile(app: App, path: string): Promise { const p = normalizePath(path); const f = app.vault.getAbstractFileByPath(p); if (!(f instanceof TFile)) return ""; return await app.vault.read(f); } export async function deleteRecursive(app: App, path: string): Promise { const p = normalizePath(path); const f = app.vault.getAbstractFileByPath(p); if (!f) return; await app.vault.delete(f, true); } export async function rename(app: App, oldPath: string, newPath: string): Promise { const f = app.vault.getAbstractFileByPath(normalizePath(oldPath)); if (!f) return; await app.fileManager.renameFile(f, normalizePath(newPath)); } export async function createProject(app: App, name: string): Promise { const root = projectPath(name); await ensureFolder(app, projectsPath()); await ensureFolder(app, root); for (const file of PROJECT_FILES) { await ensureFile(app, normalizePath(`${root}/${file}`), ""); } await createArea(app, name, "story"); } export async function createArea( app: App, project: string, area: string, zone: Zone = "to-update", ): Promise { if (zone === "to-update") await ensureFolder(app, toUpdatePath(project)); await ensureFolder(app, areaPath(project, area, zone)); } export async function createFeature( app: App, project: string, area: string, feature: string, zone: Zone = "ready", ): Promise { return await ensureFile(app, featurePath(project, area, feature, zone), ""); } export function projectFeaturePath( project: string, feature: string, zone: Zone = "ready", ): string { const file = feature.endsWith(".md") ? feature : `${feature}.md`; return normalizePath(`${zoneRootPath(project, zone)}/${file}`); } export async function createProjectFeature( app: App, project: string, feature: string, zone: Zone = "to-update", ): Promise { if (zone === "to-update") await ensureFolder(app, toUpdatePath(project)); return await ensureFile(app, projectFeaturePath(project, feature, zone), ""); } export function isProjectRootFile(name: string): boolean { return (PROJECT_FILES as readonly string[]).includes(name); } export interface ProjectFileLocation { project: string; area?: string; zone: Zone; } export function parseProjectFilePath(path: string): ProjectFileLocation | null { if (!path.endsWith(".md")) return null; const parts = normalizePath(path).split("/"); if (parts[0] !== PROJECTS_ROOT) return null; if (parts.length < 3) return null; const project = parts[1]; const rest = parts.slice(2); let zone: Zone = "ready"; if (rest[0] === TO_UPDATE_DIR) { zone = "to-update"; rest.shift(); } if (rest.length === 1) return { project, zone }; if (rest.length === 2) return { project, area: rest[0], zone }; return null; }