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 PROJECTS_ROOT = "projects";
export const TO_UPDATE_DIR = "_to-update"; 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_VIEW = "projektkontext-projects";
export const VIEW_TYPE_PROJECT_DETAILS_VIEW = "projektkontext-overview"; export const VIEW_TYPE_PROJECT_DETAILS_VIEW = "projektkontext-overview";

View File

@@ -1,7 +1,7 @@
import { App, TFile, TFolder, normalizePath } from "obsidian"; 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 { export function projectsPath(): string {
return PROJECTS_ROOT; return PROJECTS_ROOT;
@@ -15,8 +15,14 @@ export function toUpdatePath(project: string): string {
return normalizePath(`${PROJECTS_ROOT}/${project}/${TO_UPDATE_DIR}`); 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 { 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 { 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 []; if (!(folder instanceof TFolder)) return [];
return folder.children return folder.children
.filter((c): c is TFolder => c instanceof TFolder) .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)); .sort((a, b) => a.name.localeCompare(b.name));
} }
@@ -65,7 +71,7 @@ export function listMarkdownFiles(app: App, path: string, exclude: string[] = []
return folder.children return folder.children
.filter((c): c is TFile => c instanceof TFile && c.extension === "md") .filter((c): c is TFile => c instanceof TFile && c.extension === "md")
.filter((f) => !exclude.includes(f.name)) .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)); .sort((a, b) => a.basename.localeCompare(b.basename));
} }
@@ -105,7 +111,7 @@ export async function createCollection(
collection: string, collection: string,
zone: Zone = "to-update", zone: Zone = "to-update",
): Promise<void> { ): 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)); await ensureFolder(app, collectionPath(project, collection, zone));
} }
@@ -134,7 +140,7 @@ export async function createProjectFeature(
feature: string, feature: string,
zone: Zone = "to-update", zone: Zone = "to-update",
): Promise<TFile> { ): 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), ""); 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) { if (rest[0] === TO_UPDATE_DIR) {
zone = "to-update"; zone = "to-update";
rest.shift(); rest.shift();
} else if (rest[0] === IDEAS_DIR) {
zone = "ideas";
rest.shift();
} }
if (rest.length === 1) return { project, zone }; if (rest.length === 1) return { project, zone };
if (rest.length === 2) return { project, collection: rest[0], zone }; if (rest.length === 2) return { project, collection: rest[0], zone };

View File

@@ -16,12 +16,10 @@ import {
PROJECT_FILES, PROJECT_FILES,
CORE_FILE, CORE_FILE,
TARGET_FILE, TARGET_FILE,
TO_UPDATE_DIR,
} from "../const"; } from "../const";
import { import {
Zone, Zone,
projectPath, projectPath,
toUpdatePath,
zoneRootPath, zoneRootPath,
listFolders, listFolders,
listMarkdownFiles, listMarkdownFiles,
@@ -147,35 +145,33 @@ export class ProjectDetailsView extends ItemView {
private renderCollections(parent: HTMLElement): void { private renderCollections(parent: HTMLElement): void {
const section = parent.createDiv({ cls: "pk-areas-section" }); 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 ready = this.collectZoneItems("ready");
const toUpdate = this.collectZoneItems("to-update"); const toUpdate = this.collectZoneItems("to-update");
const ideas = this.collectZoneItems("ideas");
this.renderZone(section, "ready", ready); this.renderZone(section, "ready", ready);
section.createEl("hr", { cls: "pk-zone-divider" }); section.createEl("hr", { cls: "pk-zone-divider" });
this.renderZone(section, "to-update", toUpdate); 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[] } { private collectZoneItems(zone: Zone): { collections: TFolder[]; features: TFile[] } {
const root = zoneRootPath(this.project, zone); const root = zoneRootPath(this.project, zone);
const folders = listFolders(this.app, root); const folders = listFolders(this.app, root);
const collections = zone === "ready"
? folders.filter((f) => f.name !== TO_UPDATE_DIR)
: folders;
const features = listMarkdownFiles( const features = listMarkdownFiles(
this.app, this.app,
root, root,
zone === "ready" ? [...PROJECT_FILES] : [], 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( private renderZone(
@@ -187,6 +183,14 @@ export class ProjectDetailsView extends ItemView {
cls: `pk-areas-flex pk-zone-${zone}`, cls: `pk-areas-flex pk-zone-${zone}`,
attr: { "data-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) => { flex.addEventListener("dragover", (ev) => {
if (!ev.dataTransfer?.types.includes(PK_DND_MIME)) return; if (!ev.dataTransfer?.types.includes(PK_DND_MIME)) return;
ev.preventDefault(); ev.preventDefault();
@@ -214,7 +218,7 @@ export class ProjectDetailsView extends ItemView {
this.renderProjectFeatureCard(flex, zone, f); this.renderProjectFeatureCard(flex, zone, f);
} }
if (items.collections.length === 0 && items.features.length === 0) { 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.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
menu(ev, [ 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 umbenennen", icon: "pencil", onClick: () => this.openRenameCollectionAt(folderPath, collection.name, takenCollections) },
{ title: "Collection löschen", icon: "trash", onClick: () => this.openDeletePath(folderPath) }, { title: "Collection löschen", icon: "trash", onClick: () => this.openDeletePath(folderPath) },
]); ]);
@@ -330,7 +334,7 @@ export class ProjectDetailsView extends ItemView {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
menu(ev, [ 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) }, { title: "Feature löschen", icon: "trash", onClick: () => this.openDeletePath(file.path) },
]); ]);
}); });
@@ -356,7 +360,7 @@ export class ProjectDetailsView extends ItemView {
: data.name; : data.name;
const newPath = normalizePath(`${root}/${targetName}`); const newPath = normalizePath(`${root}/${targetName}`);
if (data.sourcePath === newPath) return; 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); await this.movePath(data.sourcePath, newPath);
} }
@@ -384,11 +388,7 @@ export class ProjectDetailsView extends ItemView {
await this.render(); await this.render();
} }
private openCreateProjectFeature(): void { private openCreateProjectFeature(zone: Zone = "to-update"): void {
this.openCreateProjectFeatureInZone("to-update");
}
private openCreateProjectFeatureInZone(zone: Zone): void {
const root = zoneRootPath(this.project, zone); const root = zoneRootPath(this.project, zone);
const existing = listMarkdownFiles( const existing = listMarkdownFiles(
this.app, this.app,
@@ -408,18 +408,18 @@ export class ProjectDetailsView extends ItemView {
}).open(); }).open();
} }
private openCreateCollection(): void { private openCreateCollection(zone: Zone = "to-update"): void {
const ready = listFolders(this.app, projectPath(this.project)) const allZones: Zone[] = ["ready", "to-update", "ideas"];
.filter((f) => f.name !== TO_UPDATE_DIR).map((a) => a.name); const taken = allZones.flatMap((z) =>
const inToUpdate = listFolders(this.app, toUpdatePath(this.project)).map((a) => a.name); listFolders(this.app, zoneRootPath(this.project, z)).map((a) => a.name),
const taken = [...ready, ...inToUpdate]; );
new NameModal(this.app, { new NameModal(this.app, {
title: "Neue Collection", title: "Neue Collection",
label: "Collection-Name", label: "Collection-Name",
cta: "Erstellen", cta: "Erstellen",
validate: (n) => validateName(n, taken), validate: (n) => validateName(n, taken),
onSubmit: async (name) => { onSubmit: async (name) => {
await createCollection(this.app, this.project, name); await createCollection(this.app, this.project, name, zone);
await this.render(); await this.render();
}, },
}).open(); }).open();

View File

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