This commit is contained in:
2026-05-05 09:24:35 +02:00
parent ed26305193
commit b98a998689
4 changed files with 47 additions and 36 deletions

View File

@@ -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";

View File

@@ -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<void> {
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<TFile> {
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 };

View File

@@ -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();

View File

@@ -249,6 +249,7 @@
text-align: center;
padding: 30px 12px;
pointer-events: none;
column-span: all;
}
.pk-area-card {