diff --git a/main.ts b/main.ts index 7e922b9..f8f9630 100644 --- a/main.ts +++ b/main.ts @@ -1,30 +1,30 @@ import { MarkdownView, Platform, Plugin, WorkspaceLeaf } from "obsidian"; import { - VIEW_TYPE_PROJECTS, - VIEW_TYPE_OVERVIEW, - VIEW_TYPE_DETAILS, + VIEW_TYPE_PROJECT_VIEW, + VIEW_TYPE_PROJECT_DETAILS_VIEW, + VIEW_TYPE_COLLECTION_VIEW, RIBBON_ICON, } from "./src/const"; -import { ProjectsView } from "./src/views/ProjectsView"; -import { OverviewView } from "./src/views/OverviewView"; -import { DetailsView } from "./src/views/DetailsView"; +import { ProjectView } from "./src/views/ProjectView"; +import { ProjectDetailsView } from "./src/views/ProjectDetailsView"; +import { CollectionView } from "./src/views/CollectionView"; import { parseProjectFilePath } from "./src/fs"; import { BreadcrumbSegment, injectMobileBreadcrumb } from "./src/ui"; export default class ProjektkontextPlugin extends Plugin { async onload(): Promise { - this.registerView(VIEW_TYPE_PROJECTS, (leaf) => new ProjectsView(leaf)); - this.registerView(VIEW_TYPE_OVERVIEW, (leaf) => new OverviewView(leaf)); - this.registerView(VIEW_TYPE_DETAILS, (leaf) => new DetailsView(leaf)); + this.registerView(VIEW_TYPE_PROJECT_VIEW, (leaf) => new ProjectView(leaf)); + this.registerView(VIEW_TYPE_PROJECT_DETAILS_VIEW, (leaf) => new ProjectDetailsView(leaf)); + this.registerView(VIEW_TYPE_COLLECTION_VIEW, (leaf) => new CollectionView(leaf)); this.addRibbonIcon(RIBBON_ICON, "Projekte", () => { - void this.activateProjectsView(); + void this.activateProjectView(); }); this.addCommand({ id: "open-projects", name: "Projekte öffnen", - callback: () => void this.activateProjectsView(), + callback: () => void this.activateProjectView(), }); if (Platform.isMobile) { @@ -41,12 +41,12 @@ export default class ProjektkontextPlugin extends Plugin { async onunload(): Promise {} - async activateProjectsView(): Promise { + async activateProjectView(): Promise { const { workspace } = this.app; - let leaf: WorkspaceLeaf | null = workspace.getLeavesOfType(VIEW_TYPE_PROJECTS)[0] ?? null; + let leaf: WorkspaceLeaf | null = workspace.getLeavesOfType(VIEW_TYPE_PROJECT_VIEW)[0] ?? null; if (!leaf) { leaf = workspace.getLeaf(false); - await leaf.setViewState({ type: VIEW_TYPE_PROJECTS, active: true }); + await leaf.setViewState({ type: VIEW_TYPE_PROJECT_VIEW, active: true }); } workspace.revealLeaf(leaf); } @@ -71,26 +71,26 @@ export default class ProjektkontextPlugin extends Plugin { const segments: BreadcrumbSegment[] = [ { label: "Projekte", - onClick: () => void leaf.setViewState({ type: VIEW_TYPE_PROJECTS, active: true }), + onClick: () => void leaf.setViewState({ type: VIEW_TYPE_PROJECT_VIEW, active: true }), }, { label: loc.project, onClick: () => void leaf.setViewState({ - type: VIEW_TYPE_OVERVIEW, + type: VIEW_TYPE_PROJECT_DETAILS_VIEW, active: true, state: { project: loc.project }, }), }, ]; - if (loc.area) { + if (loc.collection) { segments.push({ - label: loc.area, + label: loc.collection, onClick: () => void leaf.setViewState({ - type: VIEW_TYPE_DETAILS, + type: VIEW_TYPE_COLLECTION_VIEW, active: true, - state: { project: loc.project, area: loc.area }, + state: { project: loc.project, collection: loc.collection, zone: loc.zone }, }), }); } diff --git a/src/const.ts b/src/const.ts index 2eaab46..10d6d3e 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,9 +1,9 @@ export const PROJECTS_ROOT = "projects"; export const TO_UPDATE_DIR = "_to-update"; -export const VIEW_TYPE_PROJECTS = "projektkontext-projects"; -export const VIEW_TYPE_OVERVIEW = "projektkontext-overview"; -export const VIEW_TYPE_DETAILS = "projektkontext-details"; +export const VIEW_TYPE_PROJECT_VIEW = "projektkontext-projects"; +export const VIEW_TYPE_PROJECT_DETAILS_VIEW = "projektkontext-overview"; +export const VIEW_TYPE_COLLECTION_VIEW = "projektkontext-details"; export const RIBBON_ICON = "layout-grid"; diff --git a/src/fs.ts b/src/fs.ts index f0f5b9f..37a2395 100644 --- a/src/fs.ts +++ b/src/fs.ts @@ -19,18 +19,18 @@ 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 collectionPath(project: string, collection: string, zone: Zone = "ready"): string { + return normalizePath(`${zoneRootPath(project, zone)}/${collection}`); } export function featurePath( project: string, - area: string, + collection: string, feature: string, zone: Zone = "ready", ): string { const file = feature.endsWith(".md") ? feature : `${feature}.md`; - return normalizePath(`${zoneRootPath(project, zone)}/${area}/${file}`); + return normalizePath(`${zoneRootPath(project, zone)}/${collection}/${file}`); } export async function ensureFolder(app: App, path: string): Promise { @@ -54,6 +54,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("__")) .sort((a, b) => a.name.localeCompare(b.name)); } @@ -64,6 +65,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("__")) .sort((a, b) => a.basename.localeCompare(b.basename)); } @@ -94,27 +96,27 @@ export async function createProject(app: App, name: string): Promise { for (const file of PROJECT_FILES) { await ensureFile(app, normalizePath(`${root}/${file}`), ""); } - await createArea(app, name, "story"); + await createCollection(app, name, "story"); } -export async function createArea( +export async function createCollection( app: App, project: string, - area: string, + collection: string, zone: Zone = "to-update", ): Promise { if (zone === "to-update") await ensureFolder(app, toUpdatePath(project)); - await ensureFolder(app, areaPath(project, area, zone)); + await ensureFolder(app, collectionPath(project, collection, zone)); } export async function createFeature( app: App, project: string, - area: string, + collection: string, feature: string, zone: Zone = "ready", ): Promise { - return await ensureFile(app, featurePath(project, area, feature, zone), ""); + return await ensureFile(app, featurePath(project, collection, feature, zone), ""); } export function projectFeaturePath( @@ -136,13 +138,9 @@ export async function createProjectFeature( 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; + collection?: string; zone: Zone; } @@ -159,6 +157,6 @@ export function parseProjectFilePath(path: string): ProjectFileLocation | null { rest.shift(); } if (rest.length === 1) return { project, zone }; - if (rest.length === 2) return { project, area: rest[0], zone }; + if (rest.length === 2) return { project, collection: rest[0], zone }; return null; } diff --git a/src/views/DetailsView.ts b/src/views/CollectionView.ts similarity index 68% rename from src/views/DetailsView.ts rename to src/views/CollectionView.ts index d8acb0e..860346e 100644 --- a/src/views/DetailsView.ts +++ b/src/views/CollectionView.ts @@ -1,13 +1,13 @@ import { ItemView, MarkdownRenderer, WorkspaceLeaf, ViewStateResult } from "obsidian"; import { - VIEW_TYPE_DETAILS, - VIEW_TYPE_OVERVIEW, - VIEW_TYPE_PROJECTS, + VIEW_TYPE_COLLECTION_VIEW, + VIEW_TYPE_PROJECT_DETAILS_VIEW, + VIEW_TYPE_PROJECT_VIEW, RIBBON_ICON, } from "../const"; import { Zone, - areaPath, + collectionPath, featurePath, listMarkdownFiles, readFile, @@ -17,9 +17,9 @@ import { import { menu, breadcrumb, emptyState, openMarkdown } from "../ui"; import { NameModal } from "../modals/NameModal"; -export interface DetailsState extends Record { +export interface CollectionViewState extends Record { project: string; - area: string; + collection: string; zone?: Zone; } @@ -32,9 +32,9 @@ function validateName(name: string, taken: string[]): string | null { return null; } -export class DetailsView extends ItemView { +export class CollectionView extends ItemView { project = ""; - area = ""; + collection = ""; zone: Zone = "ready"; private renderToken = 0; @@ -44,27 +44,27 @@ export class DetailsView extends ItemView { } getViewType(): string { - return VIEW_TYPE_DETAILS; + return VIEW_TYPE_COLLECTION_VIEW; } getDisplayText(): string { - return this.area ? `Area: ${this.area}` : "Area"; + return this.collection ? `Collection: ${this.collection}` : "Collection"; } getIcon(): string { return RIBBON_ICON; } - async setState(state: DetailsState, result: ViewStateResult): Promise { + async setState(state: CollectionViewState, result: ViewStateResult): Promise { this.project = state?.project ?? ""; - this.area = state?.area ?? ""; + this.collection = state?.collection ?? (state as { area?: string })?.area ?? ""; this.zone = state?.zone ?? "ready"; await super.setState(state, result); await this.render(); } - getState(): DetailsState { - return { project: this.project, area: this.area, zone: this.zone }; + getState(): CollectionViewState { + return { project: this.project, collection: this.collection, zone: this.zone }; } async onOpen(): Promise { @@ -87,14 +87,18 @@ export class DetailsView extends ItemView { const token = ++this.renderToken; const root = this.containerEl.children[1] as HTMLElement; - if (!this.project || !this.area) { + if (!this.project || !this.collection) { root.empty(); root.addClass("pk-root"); - emptyState(root, "Keine Area ausgewählt."); + emptyState(root, "Keine Collection ausgewählt."); return; } - const features = listMarkdownFiles(this.app, areaPath(this.project, this.area, this.zone), []); + const features = listMarkdownFiles( + this.app, + collectionPath(this.project, this.collection, this.zone), + [], + ); const contents = await Promise.all(features.map((f) => readFile(this.app, f.path))); if (token !== this.renderToken) return; @@ -102,13 +106,13 @@ export class DetailsView extends ItemView { root.addClass("pk-root"); breadcrumb(root, [ - { label: "Projekte", onClick: () => this.openProjectsView() }, - { label: this.project, onClick: () => this.openOverview() }, - { label: this.area }, + { label: "Projekte", onClick: () => this.openProjectView() }, + { label: this.project, onClick: () => this.openProjectDetails() }, + { label: this.collection }, ]); if (features.length === 0) { - emptyState(root, "Noch keine Features. Rechtsklick → Neues Feature."); + emptyState(root, "Keine Features"); return; } @@ -130,6 +134,7 @@ export class DetailsView extends ItemView { btn.addEventListener("contextmenu", (ev) => { ev.preventDefault(); menu(ev, [ + { title: "Neues Feature", icon: "plus", onClick: () => this.openCreate() }, { title: "Feature löschen", icon: "trash", onClick: () => this.openDelete(f.basename) }, ]); }); @@ -139,7 +144,7 @@ export class DetailsView extends ItemView { private openCreate(): void { const taken = listMarkdownFiles( this.app, - areaPath(this.project, this.area, this.zone), + collectionPath(this.project, this.collection, this.zone), [], ).map((f) => f.basename); new NameModal(this.app, { @@ -148,26 +153,29 @@ export class DetailsView extends ItemView { cta: "Erstellen", validate: (n) => validateName(n, taken), onSubmit: async (name) => { - await createFeature(this.app, this.project, this.area, name, this.zone); + await createFeature(this.app, this.project, this.collection, name, this.zone); await this.render(); }, }).open(); } private async openDelete(feature: string): Promise { - await deleteRecursive(this.app, featurePath(this.project, this.area, feature, this.zone)); + await deleteRecursive( + this.app, + featurePath(this.project, this.collection, feature, this.zone), + ); await this.render(); } - private async openOverview(): Promise { + private async openProjectDetails(): Promise { await this.leaf.setViewState({ - type: VIEW_TYPE_OVERVIEW, + type: VIEW_TYPE_PROJECT_DETAILS_VIEW, active: true, state: { project: this.project }, }); } - private async openProjectsView(): Promise { - await this.leaf.setViewState({ type: VIEW_TYPE_PROJECTS, active: true }); + private async openProjectView(): Promise { + await this.leaf.setViewState({ type: VIEW_TYPE_PROJECT_VIEW, active: true }); } } diff --git a/src/views/OverviewView.ts b/src/views/ProjectDetailsView.ts similarity index 79% rename from src/views/OverviewView.ts rename to src/views/ProjectDetailsView.ts index d331d38..ce2c125 100644 --- a/src/views/OverviewView.ts +++ b/src/views/ProjectDetailsView.ts @@ -1,8 +1,17 @@ -import { ItemView, MarkdownRenderer, Notice, TFile, TFolder, WorkspaceLeaf, ViewStateResult, normalizePath } from "obsidian"; import { - VIEW_TYPE_OVERVIEW, - VIEW_TYPE_DETAILS, - VIEW_TYPE_PROJECTS, + ItemView, + MarkdownRenderer, + Notice, + TFile, + TFolder, + WorkspaceLeaf, + ViewStateResult, + normalizePath, +} from "obsidian"; +import { + VIEW_TYPE_PROJECT_DETAILS_VIEW, + VIEW_TYPE_COLLECTION_VIEW, + VIEW_TYPE_PROJECT_VIEW, RIBBON_ICON, PROJECT_FILES, CORE_FILE, @@ -20,14 +29,13 @@ import { rename, deleteRecursive, ensureFolder, - createArea, - createFeature, + createCollection, createProjectFeature, } from "../fs"; import { menu, breadcrumb, emptyState, openMarkdown } from "../ui"; import { NameModal } from "../modals/NameModal"; -export interface OverviewState extends Record { +export interface ProjectDetailsState extends Record { project: string; } @@ -47,7 +55,7 @@ function validateName(name: string, taken: string[], current?: string): string | return null; } -export class OverviewView extends ItemView { +export class ProjectDetailsView extends ItemView { project = ""; private renderToken = 0; @@ -57,7 +65,7 @@ export class OverviewView extends ItemView { } getViewType(): string { - return VIEW_TYPE_OVERVIEW; + return VIEW_TYPE_PROJECT_DETAILS_VIEW; } getDisplayText(): string { @@ -68,13 +76,13 @@ export class OverviewView extends ItemView { return RIBBON_ICON; } - async setState(state: OverviewState, result: ViewStateResult): Promise { + async setState(state: ProjectDetailsState, result: ViewStateResult): Promise { this.project = state?.project ?? ""; await super.setState(state, result); await this.render(); } - getState(): OverviewState { + getState(): ProjectDetailsState { return { project: this.project }; } @@ -114,14 +122,14 @@ export class OverviewView extends ItemView { root.addClass("pk-root"); breadcrumb(root, [ - { label: "Projekte", onClick: () => this.openProjectsView() }, + { label: "Projekte", onClick: () => this.openProjectView() }, { label: this.project }, ]); const info = root.createDiv({ cls: "pk-info-grid" }); this.renderInfoCard(info, core, corePath); this.renderInfoCard(info, target, targetPath); - this.renderAreas(root); + this.renderCollections(root); } private renderInfoCard(parent: HTMLElement, content: string, path: string): void { @@ -137,13 +145,13 @@ export class OverviewView extends ItemView { } } - private renderAreas(parent: HTMLElement): void { + 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.openCreateArea() }, + { title: "Neue Collection", icon: "plus", onClick: () => this.openCreateCollection() }, { title: "Neues Feature", icon: "plus", onClick: () => this.openCreateProjectFeature() }, ]); }); @@ -156,10 +164,10 @@ export class OverviewView extends ItemView { this.renderZone(section, "to-update", toUpdate); } - private collectZoneItems(zone: Zone): { areas: TFolder[]; features: TFile[] } { + private collectZoneItems(zone: Zone): { collections: TFolder[]; features: TFile[] } { const root = zoneRootPath(this.project, zone); const folders = listFolders(this.app, root); - const areas = zone === "ready" + const collections = zone === "ready" ? folders.filter((f) => f.name !== TO_UPDATE_DIR) : folders; const features = listMarkdownFiles( @@ -167,13 +175,13 @@ export class OverviewView extends ItemView { root, zone === "ready" ? [...PROJECT_FILES] : [], ); - return { areas, features }; + return { collections, features }; } private renderZone( parent: HTMLElement, zone: Zone, - items: { areas: TFolder[]; features: TFile[] }, + items: { collections: TFolder[]; features: TFile[] }, ): void { const flex = parent.createDiv({ cls: `pk-areas-flex pk-zone-${zone}`, @@ -198,42 +206,39 @@ export class OverviewView extends ItemView { await this.handleDropOnZone(raw, zone); }); - const takenAreas = items.areas.map((a) => a.name); - for (const area of items.areas) { - this.renderAreaCard(flex, zone, area, takenAreas); + const takenCollections = items.collections.map((a) => a.name); + for (const collection of items.collections) { + this.renderCollectionCard(flex, zone, collection, takenCollections); } for (const f of items.features) { - this.renderProjectFeatureCard(flex, f); + this.renderProjectFeatureCard(flex, zone, f); } - if (items.areas.length === 0 && items.features.length === 0) { - flex.createDiv({ - cls: "pk-zone-placeholder", - text: zone === "to-update" ? "Nothing to update" : "Empty", - }); + if (items.collections.length === 0 && items.features.length === 0) { + flex.createDiv({ cls: "pk-zone-placeholder", text: "Keine Features" }); } } - private renderAreaCard( + private renderCollectionCard( parent: HTMLElement, zone: Zone, - area: TFolder, - takenAreas: string[], + collection: TFolder, + takenCollections: string[], ): void { - const folderPath = area.path; + const folderPath = collection.path; const features = listMarkdownFiles(this.app, folderPath, []); const card = parent.createDiv({ cls: "pk-btn-card pk-area-card", attr: { role: "button", tabindex: "0", draggable: "true" }, }); - card.createEl("strong", { text: area.name }); - card.addEventListener("click", () => this.openDetails(area.name, zone)); + card.createEl("strong", { text: collection.name }); + card.addEventListener("click", () => this.openCollectionDetails(collection.name, zone)); card.addEventListener("contextmenu", (ev) => { ev.preventDefault(); ev.stopPropagation(); menu(ev, [ - { title: "Neues Feature", icon: "plus", onClick: () => this.openCreateFeatureIn(folderPath) }, - { title: "Collection umbenennen", icon: "pencil", onClick: () => this.openRenameAreaAt(folderPath, area.name, takenAreas) }, + { title: "Neue Collection", icon: "plus", onClick: () => this.openCreateCollection() }, + { title: "Collection umbenennen", icon: "pencil", onClick: () => this.openRenameCollectionAt(folderPath, collection.name, takenCollections) }, { title: "Collection löschen", icon: "trash", onClick: () => this.openDeletePath(folderPath) }, ]); }); @@ -242,7 +247,7 @@ export class OverviewView extends ItemView { ev.dataTransfer.setData(PK_DND_MIME, JSON.stringify({ kind: "collection", sourcePath: folderPath, - name: area.name, + name: collection.name, } satisfies DndPayload)); ev.dataTransfer.effectAllowed = "move"; card.addClass("pk-feature-chip-dragging"); @@ -265,17 +270,18 @@ export class OverviewView extends ItemView { if (!raw) return; ev.preventDefault(); ev.stopPropagation(); - await this.handleDropOnArea(raw, folderPath); + await this.handleDropOnCollection(raw, folderPath); }); if (features.length === 0) { - card.createDiv({ cls: "pk-empty", text: "Keine Features." }); + card.createDiv({ cls: "pk-empty", text: "Keine Features" }); } else { const list = card.createDiv({ cls: "pk-features" }); for (const f of features) { const chip = list.createDiv({ cls: "pk-feature-chip", text: f.basename }); chip.draggable = true; chip.addEventListener("dragstart", (ev) => { + ev.stopPropagation(); if (!ev.dataTransfer) return; ev.dataTransfer.setData(PK_DND_MIME, JSON.stringify({ kind: "feature", @@ -294,6 +300,7 @@ export class OverviewView extends ItemView { ev.preventDefault(); ev.stopPropagation(); menu(ev, [ + { title: "Neues Feature", icon: "plus", onClick: () => this.openCreateFeatureIn(folderPath) }, { title: "Feature löschen", icon: "trash", onClick: () => this.openDeletePath(f.path) }, ]); }); @@ -301,7 +308,7 @@ export class OverviewView extends ItemView { } } - private renderProjectFeatureCard(parent: HTMLElement, file: TFile): void { + private renderProjectFeatureCard(parent: HTMLElement, zone: Zone, file: TFile): void { const card = parent.createDiv({ cls: "pk-btn-card pk-area-card", attr: { role: "button", tabindex: "0", draggable: "true" }, @@ -323,6 +330,7 @@ export class OverviewView extends ItemView { ev.preventDefault(); ev.stopPropagation(); menu(ev, [ + { title: "Neues Feature", icon: "plus", onClick: () => this.openCreateProjectFeatureInZone(zone) }, { title: "Feature löschen", icon: "trash", onClick: () => this.openDeletePath(file.path) }, ]); }); @@ -352,12 +360,12 @@ export class OverviewView extends ItemView { await this.movePath(data.sourcePath, newPath); } - private async handleDropOnArea(raw: string, areaFolderPath: string): Promise { + private async handleDropOnCollection(raw: string, collectionFolderPath: string): Promise { const data = this.parseDnd(raw); if (!data) return; if (data.kind !== "feature") return; const file = data.name.endsWith(".md") ? data.name : `${data.name}.md`; - const newPath = normalizePath(`${areaFolderPath}/${file}`); + const newPath = normalizePath(`${collectionFolderPath}/${file}`); if (data.sourcePath === newPath) return; await this.movePath(data.sourcePath, newPath); } @@ -377,8 +385,16 @@ export class OverviewView extends ItemView { } private openCreateProjectFeature(): void { - const root = toUpdatePath(this.project); - const existing = listMarkdownFiles(this.app, root, []); + this.openCreateProjectFeatureInZone("to-update"); + } + + private openCreateProjectFeatureInZone(zone: Zone): void { + const root = zoneRootPath(this.project, zone); + const existing = listMarkdownFiles( + this.app, + root, + zone === "ready" ? [...PROJECT_FILES] : [], + ); const taken = existing.map((f) => f.basename); new NameModal(this.app, { title: "Neues Feature", @@ -386,13 +402,13 @@ export class OverviewView extends ItemView { cta: "Erstellen", validate: (n) => validateName(n, taken), onSubmit: async (name) => { - await createProjectFeature(this.app, this.project, name); + await createProjectFeature(this.app, this.project, name, zone); await this.render(); }, }).open(); } - private openCreateArea(): void { + 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); @@ -403,7 +419,7 @@ export class OverviewView extends ItemView { cta: "Erstellen", validate: (n) => validateName(n, taken), onSubmit: async (name) => { - await createArea(this.app, this.project, name); + await createCollection(this.app, this.project, name); await this.render(); }, }).open(); @@ -426,7 +442,7 @@ export class OverviewView extends ItemView { }).open(); } - private openRenameAreaAt(folderPath: string, current: string, taken: string[]): void { + private openRenameCollectionAt(folderPath: string, current: string, taken: string[]): void { new NameModal(this.app, { title: "Collection umbenennen", label: "Collection-Name", @@ -447,15 +463,15 @@ export class OverviewView extends ItemView { await this.render(); } - private async openDetails(area: string, zone: Zone): Promise { + private async openCollectionDetails(collection: string, zone: Zone): Promise { await this.leaf.setViewState({ - type: VIEW_TYPE_DETAILS, + type: VIEW_TYPE_COLLECTION_VIEW, active: true, - state: { project: this.project, area, zone }, + state: { project: this.project, collection, zone }, }); } - private async openProjectsView(): Promise { - await this.leaf.setViewState({ type: VIEW_TYPE_PROJECTS, active: true }); + private async openProjectView(): Promise { + await this.leaf.setViewState({ type: VIEW_TYPE_PROJECT_VIEW, active: true }); } } diff --git a/src/views/ProjectsView.ts b/src/views/ProjectView.ts similarity index 83% rename from src/views/ProjectsView.ts rename to src/views/ProjectView.ts index 0479bc6..a65c669 100644 --- a/src/views/ProjectsView.ts +++ b/src/views/ProjectView.ts @@ -1,5 +1,16 @@ -import { ItemView, Notice, WorkspaceLeaf, normalizePath } from "obsidian"; -import { VIEW_TYPE_PROJECTS, VIEW_TYPE_OVERVIEW, RIBBON_ICON, CORE_FILE } from "../const"; +import { + ItemView, + MarkdownRenderer, + Notice, + WorkspaceLeaf, + normalizePath, +} from "obsidian"; +import { + VIEW_TYPE_PROJECT_VIEW, + VIEW_TYPE_PROJECT_DETAILS_VIEW, + RIBBON_ICON, + CORE_FILE, +} from "../const"; import { projectsPath, projectPath, @@ -22,7 +33,7 @@ function validateName(name: string, taken: string[], current?: string): string | return null; } -export class ProjectsView extends ItemView { +export class ProjectView extends ItemView { private renderToken = 0; constructor(leaf: WorkspaceLeaf) { @@ -31,7 +42,7 @@ export class ProjectsView extends ItemView { } getViewType(): string { - return VIEW_TYPE_PROJECTS; + return VIEW_TYPE_PROJECT_VIEW; } getDisplayText(): string { @@ -79,11 +90,8 @@ export class ProjectsView extends ItemView { } const taken = projects.map((p) => p.name); - const cores = await Promise.all( - projects.map((p) => - readFile(this.app, normalizePath(`${projectPath(p.name)}/${CORE_FILE}`)), - ), - ); + const corePaths = projects.map((p) => normalizePath(`${projectPath(p.name)}/${CORE_FILE}`)); + const cores = await Promise.all(corePaths.map((p) => readFile(this.app, p))); if (token !== this.renderToken) return; root.empty(); @@ -94,13 +102,17 @@ export class ProjectsView extends ItemView { for (let i = 0; i < projects.length; i++) { const proj = projects[i]; const core = cores[i].trim(); + const corePath = corePaths[i]; const btn = grid.createDiv({ cls: "pk-btn-card", attr: { role: "button", tabindex: "0" }, }); btn.createEl("strong", { text: proj.name }); - if (core) btn.createDiv({ text: core }); - btn.addEventListener("click", () => this.openOverview(proj.name)); + if (core) { + const body = btn.createDiv({ cls: "pk-project-core" }); + void MarkdownRenderer.render(this.app, core, body, corePath, this); + } + btn.addEventListener("click", () => this.openProjectDetails(proj.name)); btn.addEventListener("contextmenu", (ev) => { ev.preventDefault(); menu(ev, [ @@ -158,9 +170,9 @@ export class ProjectsView extends ItemView { } } - private async openOverview(name: string): Promise { + private async openProjectDetails(name: string): Promise { await this.leaf.setViewState({ - type: VIEW_TYPE_OVERVIEW, + type: VIEW_TYPE_PROJECT_DETAILS_VIEW, active: true, state: { project: name }, }); diff --git a/styles.css b/styles.css index 64a5603..4184e5f 100644 --- a/styles.css +++ b/styles.css @@ -192,6 +192,17 @@ font-weight: 600; } +.pk-project-core { + font-size: 0.85em; + color: var(--text-muted); +} +.pk-project-core > p:first-child { margin-top: 0; } +.pk-project-core > p:last-child { margin-bottom: 0; } +.pk-project-core > ul:first-child, +.pk-project-core > ol:first-child { margin-top: 0; } +.pk-project-core > ul:last-child, +.pk-project-core > ol:last-child { margin-bottom: 0; } + .pk-info-card > p:first-child { margin-top: 0; } .pk-info-card > p:last-child { margin-bottom: 0; } .pk-info-card > ul:first-child,