165 lines
5.0 KiB
TypeScript
165 lines
5.0 KiB
TypeScript
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<void> {
|
|
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<TFile> {
|
|
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<string> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<TFile> {
|
|
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<TFile> {
|
|
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;
|
|
}
|