load active stream & show streams on all views, not just focused

This commit is contained in:
Ben Floyd 2025-08-19 12:34:57 -06:00
parent 5fce8ab794
commit 2e8d0eceeb
7 changed files with 559 additions and 120 deletions

575
main.ts
View file

@ -27,12 +27,19 @@ export default class StreamsPlugin extends Plugin {
viewCommandsByStreamId: Map<string, string> = new Map();
private calendarComponents: Map<string, CalendarComponent> = 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.');
}
}
}
}

View file

@ -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

View file

@ -95,9 +95,7 @@ export async function createDailyNote(app: App, folder: string): Promise<TFile |
}
export async function openStreamDate(app: App, stream: Stream, date: Date = new Date(), reuseCurrentTab: boolean = false): Promise<void> {
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) {

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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
*********************************************************/