diff --git a/src/const.ts b/src/const.ts index 10d6d3e..320a92d 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,5 +1,6 @@ export const PROJECTS_ROOT = "projects"; export const TO_UPDATE_DIR = "_to-update"; +export const IDEAS_DIR = "_ideas"; export const VIEW_TYPE_PROJECT_VIEW = "projektkontext-projects"; export const VIEW_TYPE_PROJECT_DETAILS_VIEW = "projektkontext-overview"; diff --git a/src/fs.ts b/src/fs.ts index 37a2395..bc4c461 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -1,7 +1,7 @@ import { App, TFile, TFolder, normalizePath } from "obsidian"; -import { PROJECTS_ROOT, PROJECT_FILES, TO_UPDATE_DIR } from "./const"; +import { PROJECTS_ROOT, PROJECT_FILES, TO_UPDATE_DIR, IDEAS_DIR } from "./const"; -export type Zone = "ready" | "to-update"; +export type Zone = "ready" | "to-update" | "ideas"; export function projectsPath(): string { return PROJECTS_ROOT; @@ -15,8 +15,14 @@ export function toUpdatePath(project: string): string { return normalizePath(`${PROJECTS_ROOT}/${project}/${TO_UPDATE_DIR}`); } +export function ideasPath(project: string): string { + return normalizePath(`${PROJECTS_ROOT}/${project}/${IDEAS_DIR}`); +} + export function zoneRootPath(project: string, zone: Zone): string { - return zone === "ready" ? projectPath(project) : toUpdatePath(project); + if (zone === "ready") return projectPath(project); + if (zone === "to-update") return toUpdatePath(project); + return ideasPath(project); } export function collectionPath(project: string, collection: string, zone: Zone = "ready"): string { @@ -54,7 +60,7 @@ export function listFolders(app: App, path: string): TFolder[] { if (!(folder instanceof TFolder)) return []; return folder.children .filter((c): c is TFolder => c instanceof TFolder) - .filter((c) => !c.name.startsWith("__")) + .filter((c) => !c.name.startsWith("_") || c.name.startsWith("__")) .sort((a, b) => a.name.localeCompare(b.name)); } @@ -65,7 +71,7 @@ export function listMarkdownFiles(app: App, path: string, exclude: string[] = [] return folder.children .filter((c): c is TFile => c instanceof TFile && c.extension === "md") .filter((f) => !exclude.includes(f.name)) - .filter((f) => !f.basename.startsWith("__")) + .filter((f) => !f.basename.startsWith("_") || f.basename.startsWith("__")) .sort((a, b) => a.basename.localeCompare(b.basename)); } @@ -105,7 +111,7 @@ export async function createCollection( collection: string, zone: Zone = "to-update", ): Promise { - if (zone === "to-update") await ensureFolder(app, toUpdatePath(project)); + if (zone !== "ready") await ensureFolder(app, zoneRootPath(project, zone)); await ensureFolder(app, collectionPath(project, collection, zone)); } @@ -134,7 +140,7 @@ export async function createProjectFeature( feature: string, zone: Zone = "to-update", ): Promise { - if (zone === "to-update") await ensureFolder(app, toUpdatePath(project)); + if (zone !== "ready") await ensureFolder(app, zoneRootPath(project, zone)); return await ensureFile(app, projectFeaturePath(project, feature, zone), ""); } @@ -155,6 +161,9 @@ export function parseProjectFilePath(path: string): ProjectFileLocation | null { if (rest[0] === TO_UPDATE_DIR) { zone = "to-update"; rest.shift(); + } else if (rest[0] === IDEAS_DIR) { + zone = "ideas"; + rest.shift(); } if (rest.length === 1) return { project, zone }; if (rest.length === 2) return { project, collection: rest[0], zone }; diff --git a/src/views/ProjectDetailsView.ts b/src/views/ProjectDetailsView.ts index ce2c125..66d3e68 100644 --- a/src/views/ProjectDetailsView.ts +++ b/src/views/ProjectDetailsView.ts @@ -16,12 +16,10 @@ import { PROJECT_FILES, CORE_FILE, TARGET_FILE, - TO_UPDATE_DIR, } from "../const"; import { Zone, projectPath, - toUpdatePath, zoneRootPath, listFolders, listMarkdownFiles, @@ -147,35 +145,33 @@ export class ProjectDetailsView extends ItemView { private renderCollections(parent: HTMLElement): void { const section = parent.createDiv({ cls: "pk-areas-section" }); - section.addEventListener("contextmenu", (ev) => { - if (ev.defaultPrevented) return; - ev.preventDefault(); - menu(ev, [ - { title: "Neue Collection", icon: "plus", onClick: () => this.openCreateCollection() }, - { title: "Neues Feature", icon: "plus", onClick: () => this.openCreateProjectFeature() }, - ]); - }); const ready = this.collectZoneItems("ready"); const toUpdate = this.collectZoneItems("to-update"); + const ideas = this.collectZoneItems("ideas"); this.renderZone(section, "ready", ready); section.createEl("hr", { cls: "pk-zone-divider" }); this.renderZone(section, "to-update", toUpdate); + section.createEl("hr", { cls: "pk-zone-divider" }); + this.renderZone(section, "ideas", ideas); } private collectZoneItems(zone: Zone): { collections: TFolder[]; features: TFile[] } { const root = zoneRootPath(this.project, zone); const folders = listFolders(this.app, root); - const collections = zone === "ready" - ? folders.filter((f) => f.name !== TO_UPDATE_DIR) - : folders; const features = listMarkdownFiles( this.app, root, zone === "ready" ? [...PROJECT_FILES] : [], ); - return { collections, features }; + return { collections: folders, features }; + } + + private zoneEmptyText(zone: Zone): string { + if (zone === "ready") return "Keine fertigen Features"; + if (zone === "to-update") return "Keine unfertigen Features"; + return "Keine neuen Features"; } private renderZone( @@ -187,6 +183,14 @@ export class ProjectDetailsView extends ItemView { cls: `pk-areas-flex pk-zone-${zone}`, attr: { "data-zone": zone }, }); + flex.addEventListener("contextmenu", (ev) => { + if (ev.defaultPrevented) return; + ev.preventDefault(); + menu(ev, [ + { title: "Neue Collection", icon: "plus", onClick: () => this.openCreateCollection(zone) }, + { title: "Neues Feature", icon: "plus", onClick: () => this.openCreateProjectFeature(zone) }, + ]); + }); flex.addEventListener("dragover", (ev) => { if (!ev.dataTransfer?.types.includes(PK_DND_MIME)) return; ev.preventDefault(); @@ -214,7 +218,7 @@ export class ProjectDetailsView extends ItemView { this.renderProjectFeatureCard(flex, zone, f); } if (items.collections.length === 0 && items.features.length === 0) { - flex.createDiv({ cls: "pk-zone-placeholder", text: "Keine Features" }); + flex.createDiv({ cls: "pk-zone-placeholder", text: this.zoneEmptyText(zone) }); } } @@ -237,7 +241,7 @@ export class ProjectDetailsView extends ItemView { ev.preventDefault(); ev.stopPropagation(); menu(ev, [ - { title: "Neue Collection", icon: "plus", onClick: () => this.openCreateCollection() }, + { title: "Neue Collection", icon: "plus", onClick: () => this.openCreateCollection(zone) }, { title: "Collection umbenennen", icon: "pencil", onClick: () => this.openRenameCollectionAt(folderPath, collection.name, takenCollections) }, { title: "Collection löschen", icon: "trash", onClick: () => this.openDeletePath(folderPath) }, ]); @@ -330,7 +334,7 @@ export class ProjectDetailsView extends ItemView { ev.preventDefault(); ev.stopPropagation(); menu(ev, [ - { title: "Neues Feature", icon: "plus", onClick: () => this.openCreateProjectFeatureInZone(zone) }, + { title: "Neues Feature", icon: "plus", onClick: () => this.openCreateProjectFeature(zone) }, { title: "Feature löschen", icon: "trash", onClick: () => this.openDeletePath(file.path) }, ]); }); @@ -356,7 +360,7 @@ export class ProjectDetailsView extends ItemView { : data.name; const newPath = normalizePath(`${root}/${targetName}`); if (data.sourcePath === newPath) return; - if (zone === "to-update") await ensureFolder(this.app, toUpdatePath(this.project)); + if (zone !== "ready") await ensureFolder(this.app, root); await this.movePath(data.sourcePath, newPath); } @@ -384,11 +388,7 @@ export class ProjectDetailsView extends ItemView { await this.render(); } - private openCreateProjectFeature(): void { - this.openCreateProjectFeatureInZone("to-update"); - } - - private openCreateProjectFeatureInZone(zone: Zone): void { + private openCreateProjectFeature(zone: Zone = "to-update"): void { const root = zoneRootPath(this.project, zone); const existing = listMarkdownFiles( this.app, @@ -408,18 +408,18 @@ export class ProjectDetailsView extends ItemView { }).open(); } - private openCreateCollection(): void { - const ready = listFolders(this.app, projectPath(this.project)) - .filter((f) => f.name !== TO_UPDATE_DIR).map((a) => a.name); - const inToUpdate = listFolders(this.app, toUpdatePath(this.project)).map((a) => a.name); - const taken = [...ready, ...inToUpdate]; + private openCreateCollection(zone: Zone = "to-update"): void { + const allZones: Zone[] = ["ready", "to-update", "ideas"]; + const taken = allZones.flatMap((z) => + listFolders(this.app, zoneRootPath(this.project, z)).map((a) => a.name), + ); new NameModal(this.app, { title: "Neue Collection", label: "Collection-Name", cta: "Erstellen", validate: (n) => validateName(n, taken), onSubmit: async (name) => { - await createCollection(this.app, this.project, name); + await createCollection(this.app, this.project, name, zone); await this.render(); }, }).open(); diff --git a/styles.css b/styles.css index 4184e5f..aa125e4 100644 --- a/styles.css +++ b/styles.css @@ -249,6 +249,7 @@ text-align: center; padding: 30px 12px; pointer-events: none; + column-span: all; } .pk-area-card {