diff --git a/src/components/features/fluent/components/FluentTopNavigation.ts b/src/components/features/fluent/components/FluentTopNavigation.ts index fb0576d2..7802db9e 100644 --- a/src/components/features/fluent/components/FluentTopNavigation.ts +++ b/src/components/features/fluent/components/FluentTopNavigation.ts @@ -6,6 +6,7 @@ import { Platform, Component, TFile, + ExtraButtonComponent, } from "obsidian"; import TaskProgressBarPlugin from "@/index"; import { Task } from "@/types/task"; @@ -14,6 +15,13 @@ import { Events, on } from "@/dataflow/events/Events"; export type ViewMode = "list" | "kanban" | "tree" | "calendar"; +export interface CustomNavButton { + id: string; + icon: string; + tooltip: string; + onClick: () => void; +} + export function isCompletedMark( plugin: TaskProgressBarPlugin, mark: string @@ -39,6 +47,8 @@ export class TopNavigation extends Component { private notificationCount = 0; private availableModes: ViewMode[] = ["list", "kanban", "tree", "calendar"]; private viewTabsContainer: HTMLElement | null = null; + private customButtonsContainer: HTMLElement | null = null; + private customButtons: Map = new Map(); constructor( containerEl: HTMLElement, @@ -197,6 +207,11 @@ export class TopNavigation extends Component { cls: "fluent-nav-right", }); + // Custom buttons container (for dynamic buttons from views) + this.customButtonsContainer = rightSection.createDiv({ + cls: "fluent-nav-custom-buttons", + }); + // Notification button const notificationBtn = rightSection.createDiv({ cls: "fluent-nav-icon-button", @@ -422,4 +437,76 @@ export class TopNavigation extends Component { public getCurrentViewMode(): ViewMode { return this.currentViewMode; } + + /** + * Register a custom button in the top navigation + */ + public registerCustomButton(config: CustomNavButton): void { + if (!this.customButtonsContainer) { + console.warn("[FluentTopNavigation] Custom buttons container not initialized"); + return; + } + + // Check if button already exists + if (this.customButtons.has(config.id)) { + console.warn(`[FluentTopNavigation] Button with id "${config.id}" already registered`); + return; + } + + // Create button container + const buttonContainer = this.customButtonsContainer.createDiv({ + cls: "fluent-nav-custom-button-wrapper", + }); + + // Create button using ExtraButtonComponent + const button = new ExtraButtonComponent(buttonContainer) + .setIcon(config.icon) + .setTooltip(config.tooltip) + .onClick(config.onClick); + + // Store reference + this.customButtons.set(config.id, { + buttonEl: buttonContainer, + config: config, + }); + + console.log(`[FluentTopNavigation] Registered custom button: ${config.id}`); + } + + /** + * Unregister a custom button by ID + */ + public unregisterCustomButton(id: string): void { + const button = this.customButtons.get(id); + if (!button) { + return; + } + + // Remove DOM element + button.buttonEl.remove(); + + // Remove from map + this.customButtons.delete(id); + + console.log(`[FluentTopNavigation] Unregistered custom button: ${id}`); + } + + /** + * Clear all custom buttons + */ + public clearCustomButtons(): void { + if (!this.customButtonsContainer) { + return; + } + + // Remove all button elements + this.customButtons.forEach((button) => { + button.buttonEl.remove(); + }); + + // Clear map + this.customButtons.clear(); + + console.log("[FluentTopNavigation] Cleared all custom buttons"); + } } diff --git a/src/components/features/fluent/managers/FluentComponentManager.ts b/src/components/features/fluent/managers/FluentComponentManager.ts index 5027faed..b3a153da 100644 --- a/src/components/features/fluent/managers/FluentComponentManager.ts +++ b/src/components/features/fluent/managers/FluentComponentManager.ts @@ -20,7 +20,7 @@ import { filterTasks } from "@/utils/task/task-filter-utils"; import { RootFilterState } from "@/components/features/task/filter/ViewTaskFilter"; import { QuickCaptureModal } from "@/components/features/quick-capture/modals/QuickCaptureModalWithSwitch"; import { t } from "@/translations/helper"; -import { ViewMode } from "../components/FluentTopNavigation"; +import { ViewMode, TopNavigation } from "../components/FluentTopNavigation"; import { TaskSelectionManager } from "@/components/features/task/selection/TaskSelectionManager"; type ManagedViewComponent = { @@ -85,6 +85,9 @@ export class FluentComponentManager extends Component { // Track currently visible component private currentVisibleComponent: ManagedViewComponent | null = null; + // Top Navigation reference (for registering custom buttons) + private topNavigation: TopNavigation | null = null; + // View handlers private viewHandlers: { onTaskSelected: (task: Task) => void; @@ -121,6 +124,14 @@ export class FluentComponentManager extends Component { this.viewHandlers = viewHandlers; } + /** + * Set the TopNavigation reference for managing custom buttons + */ + public setTopNavigation(nav: TopNavigation): void { + this.topNavigation = nav; + console.log("[FluentComponentManager] TopNavigation reference set"); + } + /** * Initialize all view components */ @@ -447,6 +458,12 @@ export class FluentComponentManager extends Component { console.log("[FluentComponent] Hiding all components"); this.hideAllComponents(); + // Clear custom buttons from TopNavigation before switching views + // They will be re-registered by the new view if needed + if (this.topNavigation) { + this.topNavigation.clearCustomButtons(); + } + // Check if current view supports multiple view modes and we're in a non-list mode const viewModes = this.getAvailableModesForView(viewId, project); @@ -588,6 +605,12 @@ export class FluentComponentManager extends Component { targetComponent.containerEl.show(); this.currentVisibleComponent = targetComponent; + // Set TopNavigation reference for ContentComponent to enable custom buttons + if (targetComponent === this.contentComponent && this.topNavigation) { + console.log("[FluentComponent] Setting TopNavigation reference for ContentComponent"); + this.contentComponent.setTopNavigation(this.topNavigation); + } + // Set view mode first for ContentComponent if (typeof targetComponent.setViewMode === "function") { console.log( @@ -685,6 +708,11 @@ export class FluentComponentManager extends Component { // Hide current component this.hideAllComponents(); + // Clear custom buttons before switching view modes + if (this.topNavigation) { + this.topNavigation.clearCustomButtons(); + } + // Based on the current view mode, show the appropriate component switch (viewMode) { case "list": @@ -693,6 +721,12 @@ export class FluentComponentManager extends Component { if (!this.contentComponent) return; this.contentComponent.containerEl.show(); + + // Set TopNavigation reference to enable custom buttons + if (this.topNavigation) { + this.contentComponent.setTopNavigation(this.topNavigation); + } + this.contentComponent.setViewMode(viewId as any); this.contentComponent.setIsTreeView(viewMode === "tree"); diff --git a/src/pages/FluentTaskView.ts b/src/pages/FluentTaskView.ts index 00ae3e3b..09669096 100644 --- a/src/pages/FluentTaskView.ts +++ b/src/pages/FluentTaskView.ts @@ -943,6 +943,10 @@ export class FluentTaskView extends ItemView { this.addChild(this.componentManager); this.componentManager.initializeViewComponents(); + // Set TopNavigation reference in ComponentManager + this.componentManager.setTopNavigation(this.topNavigation); + console.log("[Task Genius] TopNavigation reference passed to ComponentManager"); + // Sidebar toggle in header and responsive collapse console.log("[Task Genius] Creating sidebar toggle"); this.layoutManager.createSidebarToggle(); diff --git a/src/styles/fluent/fluent-main.css b/src/styles/fluent/fluent-main.css index 2827949b..29e03459 100644 --- a/src/styles/fluent/fluent-main.css +++ b/src/styles/fluent/fluent-main.css @@ -521,6 +521,18 @@ button.fluent-new-task-btn:hover { gap: 12px; } +/* Custom buttons container (for dynamic buttons from views) */ +.fluent-nav-custom-buttons { + display: flex; + align-items: center; + gap: 4px; +} + +.fluent-nav-custom-button-wrapper { + display: flex; + align-items: center; +} + .fluent-nav-icon-button { position: relative; width: 36px;