import { MarkdownView, Platform, Plugin, WorkspaceLeaf } from "obsidian"; import { VIEW_TYPE_PROJECT_VIEW, VIEW_TYPE_PROJECT_DETAILS_VIEW, RIBBON_ICON, } from "./src/const"; import { ProjectView } from "./src/views/ProjectView"; import { ProjectDetailsView } from "./src/views/ProjectDetailsView"; import { parseProjectFilePath, projectPath, nodeMdPath } from "./src/fs"; import { BreadcrumbSegment, injectMobileBreadcrumb, openMarkdown } from "./src/ui"; import { normalizePath } from "obsidian"; export default class ProjektkontextPlugin extends Plugin { async onload(): Promise { this.registerView(VIEW_TYPE_PROJECT_VIEW, (leaf) => new ProjectView(leaf)); this.registerView(VIEW_TYPE_PROJECT_DETAILS_VIEW, (leaf) => new ProjectDetailsView(leaf)); this.registerObsidianProtocolHandler("obsidian-graph", async (params) => { const project = params.project; if (!project) { await this.activateProjectView(); return; } const leaf = this.app.workspace.getLeaf(false); await leaf.setViewState({ type: VIEW_TYPE_PROJECT_DETAILS_VIEW, active: true, state: { project }, }); this.app.workspace.revealLeaf(leaf); }); this.addRibbonIcon(RIBBON_ICON, "Projekte", () => { void this.activateProjectView(); }); this.addCommand({ id: "open-projects", name: "Projekte öffnen", callback: () => void this.activateProjectView(), }); if (Platform.isMobile) { const reattach = () => this.reattachMobileBreadcrumbs(); this.registerEvent( this.app.workspace.on("file-open", () => requestAnimationFrame(reattach)), ); this.registerEvent( this.app.workspace.on("active-leaf-change", () => requestAnimationFrame(reattach)), ); this.registerEvent(this.app.workspace.on("layout-change", reattach)); } } async onunload(): Promise {} async activateProjectView(): Promise { const { workspace } = this.app; let leaf: WorkspaceLeaf | null = workspace.getLeavesOfType(VIEW_TYPE_PROJECT_VIEW)[0] ?? null; if (!leaf) { leaf = workspace.getLeaf(false); await leaf.setViewState({ type: VIEW_TYPE_PROJECT_VIEW, active: true }); } workspace.revealLeaf(leaf); } private reattachMobileBreadcrumbs(): void { this.app.workspace.iterateRootLeaves((leaf) => this.applyMobileBreadcrumb(leaf)); } private applyMobileBreadcrumb(leaf: WorkspaceLeaf): void { const view = leaf.view; if (!(view instanceof MarkdownView)) return; const file = view.file; if (!file) { injectMobileBreadcrumb(view, []); return; } const loc = parseProjectFilePath(file.path); if (!loc) { injectMobileBreadcrumb(view, []); return; } const segments: BreadcrumbSegment[] = [ { label: "Projekte", onClick: () => void leaf.setViewState({ type: VIEW_TYPE_PROJECT_VIEW, active: true }), }, { label: loc.project, onClick: () => void leaf.setViewState({ type: VIEW_TYPE_PROJECT_DETAILS_VIEW, active: true, state: { project: loc.project }, }), }, ]; let folder = projectPath(loc.project); const last = loc.path[loc.path.length - 1] ?? ""; const isNodeMd = loc.path.length >= 2 && last.endsWith(".md") && last.replace(/\.md$/, "") === loc.path[loc.path.length - 2]; const ancestorCount = isNodeMd ? loc.path.length - 2 : loc.path.length - 1; for (let i = 0; i < ancestorCount; i++) { const name = loc.path[i]; folder = normalizePath(`${folder}/${name}`); const md = nodeMdPath(folder); segments.push({ label: name, onClick: () => void openMarkdown(this.app, md, leaf), }); } segments.push({ label: file.basename }); injectMobileBreadcrumb(view, segments); } }