mirror of
https://github.com/bfloydd/streams.git
synced 2026-07-22 05:49:02 +00:00
load active stream & show streams on all views, not just focused
This commit is contained in:
parent
5fce8ab794
commit
2e8d0eceeb
7 changed files with 559 additions and 120 deletions
575
main.ts
575
main.ts
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
30
styles.css
30
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
|
||||
*********************************************************/
|
||||
|
|
|
|||
Loading…
Reference in a new issue