diff --git a/main.ts b/main.ts index 4639111..0149ee7 100644 --- a/main.ts +++ b/main.ts @@ -27,12 +27,19 @@ export default class StreamsPlugin extends Plugin { viewCommandsByStreamId: Map = new Map(); private calendarComponents: Map = new Map(); public log: Logger; + private isInitializing: boolean = true; async onload() { this.log = new Logger('Streams'); await this.loadSettings(); + // Log the restored active stream state + if (this.settings.activeStreamId) { + const activeStream = this.settings.streams.find(s => s.id === this.settings.activeStreamId); + this.log.debug(`Restored active stream: ${activeStream?.name || 'Unknown'} (${this.settings.activeStreamId})`); + } + this.registerPluginViews(); this.registerEventHandlers(); this.initializeAllRibbonIcons(); @@ -45,7 +52,19 @@ export default class StreamsPlugin extends Plugin { this.addSettingTab(new StreamsSettingTab(this.app, this)); this.initializeActiveView(); this.logInitialState(); - + + // Final refresh to ensure all calendar components are created + setTimeout(() => { + this.refreshCalendarComponentsForNewViews(); + }, 500); + + // Set up periodic refresh to ensure calendar components stay visible + this.setupPeriodicRefresh(); + + // Create global stream indicator + this.createGlobalStreamIndicator(); + + this.isInitializing = false; this.log.info('Streams plugin loaded'); } @@ -68,14 +87,91 @@ export default class StreamsPlugin extends Plugin { } } }); + + // Add a command to show current active stream + this.addCommand({ + id: 'show-active-stream', + name: 'Show current active stream', + callback: () => { + const activeStream = this.getActiveStream(); + if (activeStream) { + new Notice(`Active stream: ${activeStream.name}`); + this.log.debug(`Active stream: ${activeStream.name} (${activeStream.id})`); + } else { + new Notice('No active stream set'); + this.log.debug('No active stream set'); + } + } + }); + + // Add a command to restore active stream from settings + this.addCommand({ + id: 'restore-active-stream', + name: 'Restore active stream from settings', + callback: () => { + if (this.settings.activeStreamId) { + const activeStream = this.settings.streams.find(s => s.id === this.settings.activeStreamId); + if (activeStream) { + new Notice(`Restored active stream: ${activeStream.name}`); + this.log.debug(`Manually restored active stream: ${activeStream.name} (${activeStream.id})`); + } else { + new Notice('Active stream ID not found in streams list'); + this.log.debug(`Active stream ID ${this.settings.activeStreamId} not found in streams list`); + } + } else { + new Notice('No active stream ID in settings'); + this.log.debug('No active stream ID in settings to restore'); + } + } + }); + + // Add a command to force refresh all calendar components + this.addCommand({ + id: 'force-refresh-calendar-components', + name: 'Force refresh all calendar components', + callback: () => { + this.forceRefreshAllCalendarComponents(); + new Notice('Calendar components refreshed'); + } + }); + + // Add a command to show calendar component status + this.addCommand({ + id: 'show-calendar-component-status', + name: 'Show calendar component status', + callback: () => { + const totalComponents = this.calendarComponents.size; + const openStreamFiles = this.app.workspace.getLeavesOfType('markdown') + .filter(leaf => { + const view = leaf.view as MarkdownView; + return view?.file?.path && this.fileBelongsToStream(view.file.path); + }).length; + + new Notice(`Calendar components: ${totalComponents}/${openStreamFiles} stream files`); + this.log.debug(`Calendar component status: ${totalComponents}/${openStreamFiles} stream files`); + } + }); + + // Add a command to refresh global stream indicator + this.addCommand({ + id: 'refresh-global-stream-indicator', + name: 'Refresh global stream indicator', + callback: () => { + this.createGlobalStreamIndicator(); + new Notice('Global stream indicator refreshed'); + } + }); } private logInitialState(): void { - this.log.debug("=== INITIAL STREAM RIBBON STATES ==="); this.settings.streams.forEach(stream => { - this.log.debug(`Stream ${stream.id} (${stream.name}): Today=${stream.showTodayInRibbon}, View=${stream.showFullStreamInRibbon}`); + this.log.debug(`Stream ${stream.name}: Today=${stream.showTodayInRibbon}, View=${stream.showFullStreamInRibbon}`); }); - this.log.debug("==================================="); + + if (this.settings.activeStreamId) { + const activeStream = this.settings.streams.find(s => s.id === this.settings.activeStreamId); + this.log.debug(`Active stream: ${activeStream?.name || 'Unknown'}`); + } } private registerPluginViews(): void { @@ -117,9 +213,16 @@ export default class StreamsPlugin extends Plugin { const streamId = state?.state?.streamId; let stream = this.settings.streams.find(s => s.id === streamId); - // Fall back to first stream or create dummy if none exists + // If no stream found in view state, try to use the saved active stream + if (!stream && this.settings.activeStreamId) { + stream = this.settings.streams.find(s => s.id === this.settings.activeStreamId); + this.log.debug(`View state missing streamId, using saved active stream: ${stream?.name || 'Unknown'} (${this.settings.activeStreamId})`); + } + + // Fall back to first stream only if no active stream is saved if (!stream && this.settings.streams.length > 0) { stream = this.settings.streams[0]; + this.log.debug(`No active stream saved, falling back to first stream: ${stream.name}`); } else if (!stream) { stream = { id: "default", @@ -136,6 +239,7 @@ export default class StreamsPlugin extends Plugin { todayBorderColor: "var(--text-accent)", viewBorderColor: "var(--text-success)" }; + this.log.debug('No streams configured, using default stream'); } return new StreamView(leaf, this.app, stream); @@ -319,6 +423,12 @@ export default class StreamsPlugin extends Plugin { }); this.calendarComponents.clear(); + // Clean up global stream indicator + const existingIndicator = document.querySelector('.streams-global-indicator'); + if (existingIndicator) { + existingIndicator.remove(); + } + this.commandsByStreamId.clear(); this.viewCommandsByStreamId.clear(); } @@ -328,10 +438,36 @@ export default class StreamsPlugin extends Plugin { this.registerEvent( this.app.workspace.on('active-leaf-change', (leaf) => { this.log.debug('Active leaf changed'); - if (leaf?.view instanceof MarkdownView) { - this.updateCalendarComponent(leaf); - } else if (leaf?.view.getViewType() === CREATE_FILE_VIEW_TYPE) { - this.updateCalendarComponentForCreateView(leaf); + // Add a small delay to ensure the view is fully initialized + setTimeout(() => { + if (leaf?.view instanceof MarkdownView) { + this.updateCalendarComponent(leaf); + } else if (leaf?.view.getViewType() === CREATE_FILE_VIEW_TYPE) { + this.updateCalendarComponentForCreateView(leaf); + } + }, 100); + }) + ); + + // Handle new leaves being created (new tabs opened) + this.registerEvent( + this.app.workspace.on('layout-change', () => { + // Small delay to ensure new views are fully initialized + setTimeout(() => { + this.refreshCalendarComponentsForNewViews(); + }, 200); + }) + ); + + // Handle file open events to ensure calendar components are created + this.registerEvent( + this.app.workspace.on('file-open', (file) => { + if (file) { + this.log.debug(`File opened: ${file.path}`); + // Small delay to ensure the view is fully initialized + setTimeout(() => { + this.ensureCalendarComponentForFile(file.path); + }, 100); } }) ); @@ -369,23 +505,49 @@ export default class StreamsPlugin extends Plugin { } private initializeActiveView(): void { - const activeLeaf = this.app.workspace.getActiveViewOfType(MarkdownView)?.leaf; - if (activeLeaf) { - this.updateCalendarComponent(activeLeaf); - } + this.log.debug('Initializing calendar components for all open views'); + + // Get all open leaves + const allLeaves = this.app.workspace.getLeavesOfType('markdown'); + this.log.debug(`Found ${allLeaves.length} open markdown leaves`); + + // Initialize calendar components for ALL open stream files + allLeaves.forEach(leaf => { + const view = leaf.view as MarkdownView; + if (view?.file?.path) { + const filePath = view.file.path; + // Create component for ALL stream files, regardless of focus + if (this.fileBelongsToStream(filePath)) { + if (!this.calendarComponents.has(filePath)) { + this.log.debug(`Creating calendar component for stream file: ${filePath}`); + this.updateCalendarComponent(leaf); + } else { + this.log.debug(`Calendar component already exists for stream file: ${filePath}`); + } + } else { + this.log.debug(`Skipping non-stream file: ${filePath}`); + } + } + }); + + this.log.debug(`Initialized ${this.calendarComponents.size} calendar components`); } public updateCalendarComponent(leaf: WorkspaceLeaf) { const view = leaf.view as MarkdownView; const filePath = view.file?.path; - this.log.debug('Updating calendar component for file:', filePath); + this.log.debug(`Updating calendar component for file: ${filePath}`); + this.log.debug(`Current calendar components: ${this.calendarComponents.size}`); - // Clean up existing components - this.calendarComponents.forEach((component) => { - component.destroy(); - }); - this.calendarComponents.clear(); + // Only clean up components for this specific leaf/file + const componentId = filePath || crypto.randomUUID(); + const existingComponent = this.calendarComponents.get(componentId); + if (existingComponent) { + this.log.debug(`Destroying existing calendar component for: ${componentId}`); + existingComponent.destroy(); + this.calendarComponents.delete(componentId); + } // Exit early if there's no file path or the calendar is disabled if (!filePath || !this.settings.showCalendarComponent) { @@ -400,52 +562,21 @@ export default class StreamsPlugin extends Plugin { }))); // Find which stream this file belongs to - const stream = this.settings.streams.find(s => { - // Skip streams with empty folders - if (!s.folder || s.folder.trim() === '') { - return false; - } - - // Normalize paths for comparison - const normalizedFilePath = filePath.split(/[/\\]/).filter(Boolean); - const normalizedStreamPath = s.folder.split(/[/\\]/).filter(Boolean); - - // If stream path is empty, skip this stream - if (normalizedStreamPath.length === 0) { - return false; - } - - // Check if the file path starts with the stream path - const isInStreamFolder = normalizedStreamPath.every((part, index) => { - // Check bounds - if (index >= normalizedFilePath.length) { - return false; - } - return normalizedStreamPath[index] === normalizedFilePath[index]; - }); - - this.log.debug(`Checking stream "${s.name}" (${s.folder}):`, { - isInStreamFolder, - normalizedFilePath, - normalizedStreamPath - }); - - return isInStreamFolder; - }); + const stream = this.getStreamForFile(filePath); if (stream) { this.log.debug(`File belongs to stream: ${stream.name} (${stream.folder})`); const component = new CalendarComponent(leaf, stream, this.app, this.settings.reuseCurrentTab, this.settings.streams, this); - const componentId = filePath || crypto.randomUUID(); this.calendarComponents.set(componentId, component); - // Set this as the active stream - this.setActiveStream(stream.id); - - this.log.debug('Calendar component created successfully'); + // Don't automatically set active stream from calendar component updates + // Active stream should only be set by explicit user actions + this.log.debug(`Calendar component created successfully for: ${componentId}`); } else { this.log.debug('File does not belong to any stream'); } + + this.log.debug(`Calendar components after update: ${this.calendarComponents.size}`); } private async showStreamSelectionModal(filePath: string) { @@ -500,11 +631,17 @@ export default class StreamsPlugin extends Plugin { return; } - // Clean up existing components - this.calendarComponents.forEach((component) => { - component.destroy(); - }); - this.calendarComponents.clear(); + // Only clean up create file view components + const state = view.getState(); + if (state?.stream) { + const stream = state.stream as Stream; + const componentId = 'create-file-view-' + stream.id; + const existingComponent = this.calendarComponents.get(componentId); + if (existingComponent) { + existingComponent.destroy(); + this.calendarComponents.delete(componentId); + } + } if (!this.settings.showCalendarComponent) { this.log.debug('Calendar component is disabled in settings'); @@ -543,8 +680,9 @@ export default class StreamsPlugin extends Plugin { const formattedDate = dateString.split('T')[0]; component.setCurrentViewedDate(formattedDate); - // Set this as the active stream - this.setActiveStream(stream.id); + // Don't automatically set active stream from calendar component updates + // Active stream should only be set by explicit user actions + this.log.debug('Calendar component for create view created - not setting active stream automatically'); const componentId = 'create-file-view-' + stream.id; this.calendarComponents.set(componentId, component); @@ -590,14 +728,234 @@ export default class StreamsPlugin extends Plugin { component.updateStreamsList(this.settings.streams); }); } + + public forceRefreshAllCalendarComponents() { + this.log.debug('Force refreshing all calendar components'); + + // Clear all existing components + this.calendarComponents.forEach(component => { + component.destroy(); + }); + this.calendarComponents.clear(); + + // Recreate components for all open stream files + this.refreshCalendarComponentsForNewViews(); + + this.log.debug(`Force refresh complete. Total components: ${this.calendarComponents.size}`); + } + + public refreshCalendarComponentsForNewViews() { + this.log.debug('Refreshing calendar components for all views'); + + // Get all current markdown leaves + const allLeaves = this.app.workspace.getLeavesOfType('markdown'); + + // Ensure ALL stream files have calendar components + allLeaves.forEach(leaf => { + const view = leaf.view as MarkdownView; + if (view?.file?.path) { + const filePath = view.file.path; + // Create component for ALL stream files, regardless of focus + if (this.fileBelongsToStream(filePath)) { + if (!this.calendarComponents.has(filePath)) { + this.log.debug(`Creating missing calendar component for stream file: ${filePath}`); + this.updateCalendarComponent(leaf); + } else { + this.log.debug(`Calendar component already exists for stream file: ${filePath}`); + } + } + } + }); + + this.log.debug(`Total calendar components after refresh: ${this.calendarComponents.size}`); + } + + public ensureCalendarComponentForFile(filePath: string) { + this.log.debug(`Ensuring calendar component exists for file: ${filePath}`); + + // Find the leaf that contains this file + const leaf = this.app.workspace.getLeavesOfType('markdown').find(leaf => { + const view = leaf.view as MarkdownView; + return view?.file?.path === filePath; + }); + + if (leaf && !this.calendarComponents.has(filePath)) { + this.log.debug(`Creating missing calendar component for file: ${filePath}`); + this.updateCalendarComponent(leaf); + } else if (this.calendarComponents.has(filePath)) { + this.log.debug(`Calendar component already exists for file: ${filePath}`); + } else { + this.log.debug(`No leaf found for file: ${filePath}`); + } + } + + private fileBelongsToStream(filePath: string): boolean { + if (!filePath) return false; + + const result = this.settings.streams.some(stream => { + // Skip streams with empty folders + if (!stream.folder || stream.folder.trim() === '') { + return false; + } + + // Normalize paths for comparison + const normalizedFilePath = filePath.split(/[/\\]/).filter(Boolean); + const normalizedStreamPath = stream.folder.split(/[/\\]/).filter(Boolean); + + // If stream path is empty, skip this stream + if (normalizedStreamPath.length === 0) { + return false; + } + + // Check if the file path starts with the stream path + return normalizedStreamPath.every((part, index) => { + // Check bounds + if (index >= normalizedFilePath.length) { + return false; + } + return normalizedStreamPath[index] === normalizedFilePath[index]; + }); + }); + + if (result) { + this.log.debug(`File ${filePath} belongs to a stream`); + } + + return result; + } + + private getStreamForFile(filePath: string): Stream | null { + if (!filePath) return null; + + return this.settings.streams.find(stream => { + // Skip streams with empty folders + if (!stream.folder || stream.folder.trim() === '') { + return false; + } + + // Normalize paths for comparison + const normalizedFilePath = filePath.split(/[/\\]/).filter(Boolean); + const normalizedStreamPath = stream.folder.split(/[/\\]/).filter(Boolean); + + // Check if the file path starts with the stream path + return normalizedStreamPath.every((part, index) => { + // Check bounds + if (index >= normalizedFilePath.length) { + return false; + } + return normalizedStreamPath[index] === normalizedFilePath[index]; + }); + }) || null; + } + + private setupPeriodicRefresh(): void { + // Refresh calendar components every 2 seconds to ensure they stay visible + setInterval(() => { + if (!this.isInitializing) { + this.refreshCalendarComponentsForNewViews(); + // Also ensure global stream indicator exists + this.ensureGlobalStreamIndicator(); + } + }, 2000); + + this.log.debug('Periodic calendar component refresh enabled (every 2 seconds)'); + } + + private createGlobalStreamIndicator(): void { + // Create a global stream indicator that's always visible + // Use the status bar container that Obsidian provides + const statusBarContainer = document.querySelector('.status-bar'); + if (!statusBarContainer) return; + + // Remove existing indicator if it exists + const existingIndicator = document.querySelector('.streams-global-indicator'); + if (existingIndicator) { + existingIndicator.remove(); + } + + // Create the global indicator + const indicator = statusBarContainer.createEl('div', { + cls: 'streams-global-indicator', + text: this.getGlobalStreamIndicatorText() + }); + + // Add click handler to show stream selection + indicator.addEventListener('click', () => { + this.showGlobalStreamSelection(); + }); + + this.log.debug('Global stream indicator created'); + } + + private getGlobalStreamIndicatorText(): string { + if (this.settings.activeStreamId) { + const activeStream = this.settings.streams.find(s => s.id === this.settings.activeStreamId); + if (activeStream) { + return `📅 ${activeStream.name}`; + } + } + return '📅 No Stream'; + } + + private showGlobalStreamSelection(): void { + // Show a simple stream selection modal + const modal = new StreamSelectionModal(this.app, this.settings.streams, async (selectedStream) => { + if (selectedStream) { + this.setActiveStream(selectedStream.id, true); + this.updateGlobalStreamIndicator(); + } + }); + modal.open(); + } + + private updateGlobalStreamIndicator(): void { + const indicator = document.querySelector('.streams-global-indicator'); + if (indicator) { + indicator.setText(this.getGlobalStreamIndicatorText()); + } else { + // If indicator doesn't exist, recreate it + this.log.debug('Global stream indicator not found, recreating'); + this.createGlobalStreamIndicator(); + } + } + + private ensureGlobalStreamIndicator(): void { + const indicator = document.querySelector('.streams-global-indicator'); + if (!indicator) { + this.log.debug('Global stream indicator missing, creating'); + this.createGlobalStreamIndicator(); + } + } /** * Set the currently active stream */ - public setActiveStream(streamId: string): void { + public setActiveStream(streamId: string, force: boolean = false): void { + const previousStreamId = this.settings.activeStreamId; + + // Block changes during initialization unless forced + if (this.isInitializing && previousStreamId && previousStreamId !== streamId && !force) { + this.log.debug(`Blocked active stream change during initialization: ${previousStreamId} → ${streamId}`); + return; + } + + // Block automatic changes unless forced + if (!force && !this.isInitializing && previousStreamId && previousStreamId !== streamId) { + this.log.debug(`Blocked automatic active stream change: ${previousStreamId} → ${streamId}`); + return; + } + this.settings.activeStreamId = streamId; this.saveSettings(); - this.log.debug(`Set active stream to: ${streamId}`); + + if (previousStreamId !== streamId) { + const newStream = this.settings.streams.find(s => s.id === streamId); + const previousStream = previousStreamId ? this.settings.streams.find(s => s.id === previousStreamId) : null; + this.log.debug(`Active stream changed: ${previousStream?.name || 'None'} → ${newStream?.name || 'Unknown'}`); + + // Update the global stream indicator + this.updateGlobalStreamIndicator(); + } } /** @@ -611,6 +969,7 @@ export default class StreamsPlugin extends Plugin { const activeStream = this.settings.streams.find(s => s.id === this.settings.activeStreamId); if (!activeStream) { // Clear invalid active stream ID + this.log.debug(`Invalid active stream ID found: ${this.settings.activeStreamId}, clearing it`); this.settings.activeStreamId = undefined; this.saveSettings(); return null; @@ -623,20 +982,59 @@ export default class StreamsPlugin extends Plugin { * Clear the currently active stream */ public clearActiveStream(): void { + const previousStreamId = this.settings.activeStreamId; this.settings.activeStreamId = undefined; this.saveSettings(); - this.log.debug('Cleared active stream'); + this.log.debug(`Cleared active stream: ${previousStreamId || 'None'}`); + } + + /** + * Get debug information about the current active stream state + */ + public getActiveStreamDebugInfo(): string { + const activeStream = this.getActiveStream(); + const currentFile = this.app.workspace.getActiveViewOfType(MarkdownView)?.file; + const currentFilePath = currentFile?.path; + + let info = `Active Stream: ${activeStream?.name || 'None'}\n`; + info += `Current File: ${currentFilePath || 'None'}\n`; + info += `Initialization Complete: ${!this.isInitializing}\n`; + + if (currentFilePath) { + const matchingStream = this.settings.streams.find(s => { + if (!s.folder || s.folder.trim() === '') return false; + const normalizedFilePath = currentFilePath.split(/[/\\]/).filter(Boolean); + const normalizedStreamPath = s.folder.split(/[/\\]/).filter(Boolean); + return normalizedStreamPath.every((part, index) => + index < normalizedFilePath.length && normalizedStreamPath[index] === normalizedFilePath[index] + ); + }); + info += `File Belongs To Stream: ${matchingStream?.name || 'None'}\n`; + } + + return info; + } + + /** + * Force set initialization state (for testing purposes) + */ + public setInitializationState(isInitializing: boolean): void { + this.isInitializing = isInitializing; + this.log.debug(`Initialization state: ${isInitializing}`); } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - // Ensure showCalendarComponent has a default value if not set + if (this.settings.activeStreamId) { + this.log.debug(`Loaded active stream ID: ${this.settings.activeStreamId}`); + } + + // Ensure default values are set if (this.settings.showCalendarComponent === undefined) { this.settings.showCalendarComponent = true; } - // Ensure reuseCurrentTab has a default value if not set if (this.settings.reuseCurrentTab === undefined) { this.settings.reuseCurrentTab = false; } @@ -692,10 +1090,9 @@ export default class StreamsPlugin extends Plugin { } async saveSettings(refreshUI: boolean = false) { - this.log.debug("Saving settings..."); try { await this.saveData(this.settings); - this.log.debug("Settings saved successfully"); + this.log.debug("Settings saved"); this.logSavedSettings(); this.updateAllIconVisibility(); @@ -705,11 +1102,9 @@ export default class StreamsPlugin extends Plugin { } private logSavedSettings(): void { - this.log.debug("=== SAVED STREAM STATES ==="); this.settings.streams.forEach(stream => { - this.log.debug(`Stream ${stream.id} (${stream.name}): Today=${stream.showTodayInRibbon}, View=${stream.showFullStreamInRibbon}`); + this.log.debug(`Stream ${stream.name}: Today=${stream.showTodayInRibbon}, View=${stream.showFullStreamInRibbon}`); }); - this.log.debug("=========================="); } @@ -875,54 +1270,30 @@ export default class StreamsPlugin extends Plugin { } public refreshAllCalendarComponents(): void { - // Always clean up existing components first - this.log.debug(`Refreshing all calendar components. Current setting: ${this.settings.showCalendarComponent}`); + // Clean up existing components this.calendarComponents.forEach((component) => { component.destroy(); }); this.calendarComponents.clear(); - // If components should be hidden, we're done if (!this.settings.showCalendarComponent) { - this.log.debug('Calendar components will remain hidden due to setting'); return; } - // Track if we successfully created at least one component - let componentCreated = false; - - // Otherwise, force recreation based on the current view type - this.log.debug('Recreating calendar components based on current view'); - - // Handle markdown view case - most common + // Recreate components based on current view const activeMarkdownLeaf = this.app.workspace.getActiveViewOfType(MarkdownView)?.leaf; if (activeMarkdownLeaf) { - this.log.debug('Active view is a markdown view, creating component if applicable'); this.updateCalendarComponent(activeMarkdownLeaf); - - // Check if a component was actually created - if (this.calendarComponents.size > 0) { - componentCreated = true; - this.log.debug('Calendar component successfully created for markdown view'); - } } - // If no component was created and there's an active leaf, try other view types - if (!componentCreated && this.app.workspace.activeLeaf) { + // Try create file view if no markdown component was created + if (this.calendarComponents.size === 0 && this.app.workspace.activeLeaf) { const activeLeaf = this.app.workspace.activeLeaf; const viewType = activeLeaf.view?.getViewType(); - // Handle create file view case if (viewType === CREATE_FILE_VIEW_TYPE) { - this.log.debug('Active view is a create file view, creating component'); this.updateCalendarComponentForCreateView(activeLeaf); - componentCreated = this.calendarComponents.size > 0; - } - // Stream views and other view types don't get calendar components - } - - if (!componentCreated) { - this.log.debug('No calendar component was created during refresh. This may be because no suitable view is active or the current file is not part of a stream.'); + } } } } \ No newline at end of file diff --git a/src/components/CalendarComponent.ts b/src/components/CalendarComponent.ts index 435a4a3..b7baf57 100644 --- a/src/components/CalendarComponent.ts +++ b/src/components/CalendarComponent.ts @@ -22,9 +22,10 @@ interface ViewWithContentEl extends View { interface PluginInterface { settings: { calendarCompactState: boolean; + activeStreamId?: string; }; saveSettings(): void; - setActiveStream(streamId: string): void; + setActiveStream(streamId: string, force?: boolean): void; } export class CalendarComponent extends Component { @@ -43,6 +44,26 @@ export class CalendarComponent extends Component { private streams: Stream[]; private toggleButton: HTMLElement; private plugin: PluginInterface | null; + + /** + * Get the stream name to display (active stream if available, otherwise selected stream) + */ + private getDisplayStreamName(): string { + if (this.plugin?.settings?.activeStreamId) { + const activeStream = this.streams.find(s => s.id === this.plugin!.settings.activeStreamId); + if (activeStream) { + return activeStream.name; + } + } + return this.selectedStream.name; + } + + /** + * Get the active stream ID (from plugin settings) or fall back to selected stream ID + */ + private getActiveStreamId(): string { + return this.plugin?.settings?.activeStreamId || this.selectedStream.id; + } constructor(leaf: WorkspaceLeaf, stream: Stream, app: App, reuseCurrentTab: boolean = false, streams: Stream[] = [], plugin: PluginInterface | null = null) { super(); @@ -53,6 +74,14 @@ export class CalendarComponent extends Component { this.streams = streams; this.plugin = plugin; + // Log if display stream differs from calendar stream + if (this.plugin?.settings?.activeStreamId) { + const activeStream = this.streams.find(s => s.id === this.plugin!.settings.activeStreamId); + if (activeStream && activeStream.id !== stream.id) { + this.log.debug(`Calendar: ${stream.name}, Display: ${activeStream.name}`); + } + } + this.component = document.createElement('div'); this.component.addClass('streams-calendar-component'); @@ -192,7 +221,7 @@ export class CalendarComponent extends Component { // Create the "Change Stream" section const changeStreamSection = collapsedView.createDiv('streams-calendar-change-stream'); const changeStreamText = changeStreamSection.createDiv('streams-calendar-change-stream-text'); - changeStreamText.setText(this.selectedStream.name); + changeStreamText.setText(this.getDisplayStreamName()); changeStreamSection.addEventListener('click', (e) => { e.stopPropagation(); this.toggleStreamsDropdown(); @@ -207,7 +236,7 @@ export class CalendarComponent extends Component { const todayNavButton = topNav.createDiv('streams-calendar-today-nav'); todayNavButton.setText('Today'); const streamName = topNav.createDiv('streams-calendar-name'); - streamName.setText(this.selectedStream.name); + streamName.setText(this.getDisplayStreamName()); const backButton = topNav.createDiv('streams-calendar-back'); setIcon(backButton, 'chevron-up'); backButton.setAttr('aria-label', 'Collapse calendar'); @@ -573,7 +602,9 @@ export class CalendarComponent extends Component { const streamItem = this.streamsDropdown!.createDiv('streams-calendar-stream-item'); // Add a class to indicate if this is the currently selected stream - if (stream.id === this.selectedStream.id) { + // Use the active stream ID if available, otherwise use the selected stream + const isSelected = stream.id === this.getActiveStreamId(); + if (isSelected) { streamItem.addClass('streams-calendar-stream-item-selected'); } @@ -583,7 +614,7 @@ export class CalendarComponent extends Component { streamName.setText(stream.name); // Add a checkmark for the currently selected stream - if (stream.id === this.selectedStream.id) { + if (isSelected) { const checkmark = streamItem.createDiv('streams-calendar-stream-item-checkmark'); setIcon(checkmark, 'check'); } @@ -606,6 +637,12 @@ export class CalendarComponent extends Component { changeStreamText.setText(stream.name); } + // Also update the stream name in the expanded view + const expandedStreamName = this.component.querySelector('.streams-calendar-name'); + if (expandedStreamName) { + expandedStreamName.setText(stream.name); + } + // Only update the calendar grid if it exists and needs content updates if (this.grid) { this.updateGridContent(this.grid); @@ -617,8 +654,9 @@ export class CalendarComponent extends Component { } // Set this as the active stream in the main plugin + // This is a user-initiated action, so force the change if (this.plugin && this.plugin.setActiveStream) { - this.plugin.setActiveStream(stream.id); + this.plugin.setActiveStream(stream.id, true); } // Navigate to the selected stream's daily note diff --git a/src/utils/streamUtils.ts b/src/utils/streamUtils.ts index 3541839..8fac250 100644 --- a/src/utils/streamUtils.ts +++ b/src/utils/streamUtils.ts @@ -95,9 +95,7 @@ export async function createDailyNote(app: App, folder: string): Promise { - log.debug(`==== OPEN STREAM DATE START ====`); - log.debug(`Date provided: ${date.toISOString()}`); - log.debug(`Reuse current tab: ${reuseCurrentTab}`); + log.debug(`Opening stream date: ${date.toISOString()}, reuse tab: ${reuseCurrentTab}`); if (!(date instanceof Date) || isNaN(date.getTime())) { log.error(`Invalid date provided: ${date}`); @@ -182,9 +180,8 @@ export async function openStreamDate(app: App, stream: Stream, date: Date = new }); log.debug(`Set view state with date: ${date.toISOString()} for file: ${filePath}`); - app.workspace.setActiveLeaf(leaf, { focus: true }); - log.debug(`==== OPEN STREAM DATE END (create view) ====`); - return; + app.workspace.setActiveLeaf(leaf, { focus: true }); + return; } if (file instanceof TFile) { diff --git a/src/views/AllStreamsView.ts b/src/views/AllStreamsView.ts index 5c74986..d6b0bb9 100644 --- a/src/views/AllStreamsView.ts +++ b/src/views/AllStreamsView.ts @@ -16,7 +16,7 @@ interface AppWithInternal extends App { // Interface for the streams plugin interface StreamsPlugin { - setActiveStream(streamId: string): void; + setActiveStream(streamId: string, force?: boolean): void; } export const ALL_STREAMS_VIEW_TYPE = 'streams-all-streams-view'; @@ -316,10 +316,11 @@ export class AllStreamsView extends ItemView { private setActiveStream(stream: Stream): void { // Set this as the active stream in the main plugin + // This is a user-initiated action (clicking on a stream), so force the change const appWithInternal = this.app as AppWithInternal; const plugin = appWithInternal.plugins.plugins['streams'] as StreamsPlugin; if (plugin && plugin.setActiveStream) { - plugin.setActiveStream(stream.id); + plugin.setActiveStream(stream.id, true); } } diff --git a/src/views/CreateFileView.ts b/src/views/CreateFileView.ts index 2ed0194..037e631 100644 --- a/src/views/CreateFileView.ts +++ b/src/views/CreateFileView.ts @@ -4,7 +4,7 @@ import { Logger } from '../utils/Logger'; // Interface for the streams plugin interface StreamsPlugin { - setActiveStream(streamId: string): void; + setActiveStream(streamId: string, force?: boolean): void; } // Interface for accessing app.plugins @@ -290,11 +290,12 @@ export class CreateFileView extends ItemView { private setActiveStream(): void { // Set this as the active stream in the main plugin + // This is a user-initiated action (opening a create file view), so force the change try { const appWithPlugins = this.app as unknown as AppWithPlugins; const plugin = appWithPlugins.plugins.plugins['streams']; if (plugin?.setActiveStream) { - plugin.setActiveStream(this.stream.id); + plugin.setActiveStream(this.stream.id, true); } } catch (error) { this.log.error('Error setting active stream:', error); diff --git a/src/views/StreamView.ts b/src/views/StreamView.ts index 367d841..cdeb5e7 100644 --- a/src/views/StreamView.ts +++ b/src/views/StreamView.ts @@ -13,7 +13,7 @@ interface StreamsPlugin extends Plugin { // Interface for the streams plugin interface StreamsPlugin { - setActiveStream(streamId: string): void; + setActiveStream(streamId: string, force?: boolean): void; } // Interface for accessing app.plugins @@ -387,11 +387,12 @@ export class StreamView extends ItemView { private setActiveStream(): void { // Set this as the active stream in the main plugin + // This is a user-initiated action (opening a stream view), so force the change try { const appWithPlugins = this.app as unknown as AppWithPlugins; const plugin = appWithPlugins.plugins.plugins['streams']; if (plugin?.setActiveStream) { - plugin.setActiveStream(this.stream.id); + plugin.setActiveStream(this.stream.id, true); } } catch (error) { this.log.error('Error setting active stream:', error); diff --git a/styles.css b/styles.css index 11f8798..912274c 100644 --- a/styles.css +++ b/styles.css @@ -1,3 +1,33 @@ +/********************************************************* + * GLOBAL STREAM INDICATOR STYLES + *********************************************************/ + +.streams-global-indicator { + display: inline-flex; + align-items: center; + padding: 4px 8px; + margin-left: 8px; + background: var(--background-secondary); + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + color: var(--text-normal); + transition: all 0.2s ease; + user-select: none; +} + +.streams-global-indicator:hover { + background: var(--background-modifier-hover); + border-color: var(--text-accent); + color: var(--text-accent); +} + +.streams-global-indicator:active { + transform: scale(0.95); +} + /********************************************************* * ALL STREAMS VIEW STYLES *********************************************************/