mirror of
https://github.com/bfloydd/streams.git
synced 2026-07-22 05:49:02 +00:00
remove activestream; replace by reading dynamically
This commit is contained in:
parent
27af0edb51
commit
b3001ea14b
26 changed files with 376 additions and 418 deletions
20
main.ts
20
main.ts
|
|
@ -58,6 +58,12 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI {
|
|||
}
|
||||
}
|
||||
|
||||
// Cleanup: Remove legacy activeStreamId
|
||||
if ((this.settings as any).activeStreamId !== undefined) {
|
||||
delete (this.settings as any).activeStreamId;
|
||||
needsSave = true;
|
||||
}
|
||||
|
||||
if (needsSave) {
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
|
@ -92,14 +98,7 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI {
|
|||
// PUBLIC API METHODS - Available to other plugins
|
||||
// ============================================================================
|
||||
|
||||
// Stream Management
|
||||
async setActiveStream(streamId: string, force?: boolean, suppressEvent?: boolean): Promise<void> {
|
||||
await serviceRegistry.streamManagement?.setActiveStream(streamId, force, suppressEvent);
|
||||
}
|
||||
|
||||
getActiveStream(): Stream | null {
|
||||
return serviceRegistry.streamManagement?.getActiveStream() || null;
|
||||
}
|
||||
// Stream Management methods removed (global active stream deprecated)
|
||||
|
||||
// Stream Data Access
|
||||
getStreams(): Stream[] {
|
||||
|
|
@ -151,10 +150,7 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI {
|
|||
return serviceRegistry.api?.getVersion() || { version: '1.0.0', minAppVersion: '0.15.0', name: 'Streams', id: 'streams' };
|
||||
}
|
||||
|
||||
// Stream Bar Updates
|
||||
async updateStreamBarFromFile(filePath: string): Promise<boolean> {
|
||||
return serviceRegistry.api?.updateStreamBarFromFile(filePath) || false;
|
||||
}
|
||||
|
||||
|
||||
// Internal Services (for plugin functionality)
|
||||
getFileOperationsService(): FileOperationsService | undefined {
|
||||
|
|
|
|||
4
spec.md
Normal file
4
spec.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
- remove the global active stream concept thoroughly and replace it with this more dynamic approach.
|
||||
- the calendar, it's buttons, and the home button should relate to the stream we are looking at (no longer anything to do with global streams), and remember that while the stream file is open.
|
||||
- we don't need to store active streams in data.json or anywhere else, just detect the stream by it being "active" in settings and the file is part of a stream.
|
||||
- if a stream file is not open, then don't show the streamsbar at all.
|
||||
|
|
@ -69,19 +69,14 @@ export abstract class PluginAwareSliceService extends BaseSliceService implement
|
|||
await settingsManager.saveSettings();
|
||||
}
|
||||
|
||||
protected getActiveStream(): Stream | null {
|
||||
const settingsManager = this.getSettingsManager();
|
||||
const activeStreamId = settingsManager.settings?.activeStreamId;
|
||||
if (!activeStreamId) return null;
|
||||
return this.getStreams().find(s => s.id === activeStreamId) || null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class StreamAwareSliceService extends PluginAwareSliceService implements StreamAwareService {
|
||||
abstract onStreamAdded(stream: Stream): void;
|
||||
abstract onStreamUpdated(stream: Stream): void;
|
||||
abstract onStreamRemoved(streamId: string): void;
|
||||
abstract onActiveStreamChanged(streamId: string | undefined): void;
|
||||
|
||||
}
|
||||
|
||||
export abstract class SettingsAwareSliceService extends PluginAwareSliceService implements SettingsAwareService {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export interface DefaultSettingsConfiguration {
|
|||
readonly streams: readonly [];
|
||||
readonly showStreamsBarComponent: boolean;
|
||||
readonly reuseCurrentTab: boolean;
|
||||
readonly activeStreamId: undefined;
|
||||
|
||||
readonly debugLoggingEnabled: boolean;
|
||||
readonly barStyle: 'default';
|
||||
}
|
||||
|
|
@ -79,6 +79,7 @@ export class ConfigurationService extends BaseSliceService implements Configurat
|
|||
'graph',
|
||||
'markdown',
|
||||
'streams-install-meld-view',
|
||||
'streams-create-file-view',
|
||||
'streams-create-file-view-encrypted'
|
||||
] as const
|
||||
},
|
||||
|
|
@ -106,7 +107,7 @@ export class ConfigurationService extends BaseSliceService implements Configurat
|
|||
streams: [],
|
||||
showStreamsBarComponent: true,
|
||||
reuseCurrentTab: false,
|
||||
activeStreamId: undefined,
|
||||
|
||||
debugLoggingEnabled: false,
|
||||
barStyle: 'default'
|
||||
} as const
|
||||
|
|
@ -222,7 +223,7 @@ export class ConfigurationService extends BaseSliceService implements Configurat
|
|||
streams: [...this.config.defaults.streams],
|
||||
showStreamsBarComponent: this.config.defaults.showStreamsBarComponent,
|
||||
reuseCurrentTab: this.config.defaults.reuseCurrentTab,
|
||||
activeStreamId: this.config.defaults.activeStreamId,
|
||||
|
||||
debugLoggingEnabled: this.config.defaults.debugLoggingEnabled,
|
||||
barStyle: this.config.defaults.barStyle
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ export class EventBus {
|
|||
if (!this.handlers.has(eventType)) {
|
||||
this.handlers.set(eventType, new Set());
|
||||
}
|
||||
|
||||
|
||||
this.handlers.get(eventType)!.add(handler);
|
||||
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => {
|
||||
const handlers = this.handlers.get(eventType);
|
||||
|
|
@ -118,23 +118,23 @@ export const EVENTS = {
|
|||
STREAM_ADDED: 'stream:added',
|
||||
STREAM_UPDATED: 'stream:updated',
|
||||
STREAM_REMOVED: 'stream:removed',
|
||||
ACTIVE_STREAM_CHANGED: 'stream:active-changed',
|
||||
|
||||
// ACTIVE_STREAM_CHANGED: 'stream:active-changed', - REMOVED
|
||||
|
||||
// Settings events
|
||||
SETTINGS_CHANGED: 'settings:changed',
|
||||
|
||||
|
||||
// UI events
|
||||
CALENDAR_COMPONENT_UPDATED: 'ui:calendar-updated',
|
||||
RIBBON_ICONS_UPDATED: 'ui:ribbon-updated',
|
||||
|
||||
|
||||
// File events
|
||||
FILE_OPENED: 'file:opened',
|
||||
FILE_CREATED: 'file:created',
|
||||
|
||||
|
||||
// Plugin events
|
||||
PLUGIN_LOADED: 'plugin:loaded',
|
||||
PLUGIN_UNLOADED: 'plugin:unloaded',
|
||||
|
||||
|
||||
// Error events
|
||||
ERROR_OCCURRED: 'error:occurred'
|
||||
} as const;
|
||||
|
|
|
|||
42
src/shared/StreamContextService.ts
Normal file
42
src/shared/StreamContextService.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { TFile } from 'obsidian';
|
||||
import { Stream } from './types';
|
||||
|
||||
export class StreamContextService {
|
||||
/**
|
||||
* Determines which stream a file belongs to based on its path.
|
||||
* @param file The file to check.
|
||||
* @param streams The list of available streams.
|
||||
* @returns The matching Stream or null if not found.
|
||||
*/
|
||||
public getStreamForFile(file: TFile | null, streams: Stream[]): Stream | null {
|
||||
if (!file || !streams || streams.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// iterate through streams to find a match
|
||||
// A file belongs to a stream if its path starts with the stream's folder path
|
||||
for (const stream of streams) {
|
||||
if (this.isFileInStream(file, stream)) {
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific file belongs to a specific stream.
|
||||
* @param file The file to check.
|
||||
* @param stream The stream to check against.
|
||||
*/
|
||||
public isFileInStream(file: TFile, stream: Stream): boolean {
|
||||
if (!file.path || !stream.folder) return false;
|
||||
|
||||
// Normalize paths for comparison (remove trailing slashes if any, though obsidian paths usually don't have them)
|
||||
const streamFolder = stream.folder.replace(/\/$/, '');
|
||||
|
||||
// Exact match (file is the folder? unlikely) or subdirectory match
|
||||
// We verify it starts with "folder/"
|
||||
return file.path.startsWith(streamFolder + '/');
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ export const DEFAULT_SETTINGS = {
|
|||
streams: [],
|
||||
showStreamsBarComponent: true,
|
||||
reuseCurrentTab: false,
|
||||
activeStreamId: undefined,
|
||||
debugLoggingEnabled: false,
|
||||
barStyle: 'default' as const
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export interface StreamAwareService {
|
|||
onStreamAdded(stream: Stream): void;
|
||||
onStreamUpdated(stream: Stream): void;
|
||||
onStreamRemoved(streamId: string): void;
|
||||
onActiveStreamChanged(streamId: string | undefined): void;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -106,9 +106,8 @@ export interface SettingsManager {
|
|||
/**
|
||||
* Interface for stream management operations
|
||||
*/
|
||||
// Methods removed
|
||||
export interface StreamManager {
|
||||
setActiveStream(streamId: string, force?: boolean): Promise<void>;
|
||||
getActiveStream(): Stream | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export interface StreamsSettings {
|
|||
streams: Stream[];
|
||||
showStreamsBarComponent: boolean;
|
||||
reuseCurrentTab: boolean;
|
||||
activeStreamId?: string; // ID of the currently active/selected stream
|
||||
|
||||
debugLoggingEnabled: boolean; // Whether debug logging is enabled by default
|
||||
barStyle: 'default' | 'modern'; // Style variant for the streams bar
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ export type EventData =
|
|||
// Stream events
|
||||
| { stream: Stream } // STREAM_ADDED, STREAM_UPDATED
|
||||
| { streamId: string } // STREAM_REMOVED
|
||||
| { streamId: string; previousStreamId?: string } // ACTIVE_STREAM_CHANGED
|
||||
|
||||
| { streamId: string; disabled: boolean } // STREAM_UPDATED (disabled state)
|
||||
|
||||
// Settings events
|
||||
|
|
|
|||
|
|
@ -37,29 +37,7 @@ export class APIService extends PluginAwareSliceService implements StreamsAPI {
|
|||
return streams.find(stream => stream.id === streamId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently active stream
|
||||
* @returns The active stream if set, null otherwise
|
||||
*/
|
||||
public getActiveStream(): Stream | null {
|
||||
const plugin = this.getPlugin();
|
||||
const activeStreamId = plugin.settings?.activeStreamId;
|
||||
|
||||
if (!activeStreamId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeStream = this.getStream(activeStreamId);
|
||||
if (!activeStream) {
|
||||
// Clear invalid active stream ID
|
||||
this.log(`Invalid active stream ID found: ${activeStreamId}, clearing it`);
|
||||
plugin.settings.activeStreamId = undefined;
|
||||
plugin.saveSettings();
|
||||
return null;
|
||||
}
|
||||
|
||||
return activeStream;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic stream filtering function
|
||||
|
|
@ -83,8 +61,8 @@ export class APIService extends PluginAwareSliceService implements StreamsAPI {
|
|||
const searchFolder = FileUtils.normalizePath(folderPath);
|
||||
|
||||
return streamFolder === searchFolder ||
|
||||
streamFolder.startsWith(searchFolder + '/') ||
|
||||
searchFolder.startsWith(streamFolder + '/');
|
||||
streamFolder.startsWith(searchFolder + '/') ||
|
||||
searchFolder.startsWith(streamFolder + '/');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +124,7 @@ export class APIService extends PluginAwareSliceService implements StreamsAPI {
|
|||
name: stream.name,
|
||||
folder: stream.folder,
|
||||
icon: stream.icon,
|
||||
isActive: stream.id === this.getActiveStream()?.id
|
||||
isActive: false // Global active stream concept removed
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -188,54 +166,6 @@ export class APIService extends PluginAwareSliceService implements StreamsAPI {
|
|||
return this.getStreamCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the stream bar to match an opened file
|
||||
* @param filePath The path of the file that was opened
|
||||
* @returns True if successful, false if stream not found or update failed
|
||||
*/
|
||||
public async updateStreamBarFromFile(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
// Detect stream from file path
|
||||
const stream = this.getStreamForFile(filePath);
|
||||
if (!stream) {
|
||||
this.log(`No stream found for file: ${filePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract date from file path
|
||||
const targetDate = DateUtils.extractDateFromFilePath(filePath);
|
||||
|
||||
// Update stream context and settings
|
||||
await this.updateStreamContext(stream.id, targetDate);
|
||||
|
||||
// Emit event to trigger UI refresh
|
||||
eventBus.emit(EVENTS.ACTIVE_STREAM_CHANGED, { streamId: stream.id }, 'api');
|
||||
|
||||
this.log(`Updated stream bar to "${stream.name}" for file: ${filePath} with date: ${targetDate.toISOString()}`);
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
this.error(`Failed to update stream bar for file ${filePath}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the active stream and date context
|
||||
* @param streamId The stream ID to set as active
|
||||
* @param targetDate The date to set in the date state manager
|
||||
*/
|
||||
private async updateStreamContext(streamId: string, targetDate: Date): Promise<void> {
|
||||
const plugin = this.getPlugin();
|
||||
|
||||
// Set the stream context
|
||||
plugin.settings.activeStreamId = streamId;
|
||||
await plugin.saveSettings();
|
||||
|
||||
// Update the date state manager to reflect the file's date
|
||||
const { DateStateManager } = await import('../../shared/DateStateManager');
|
||||
const dateStateManager = DateStateManager.getInstance();
|
||||
dateStateManager.setCurrentDate(targetDate);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,7 @@ export interface StreamsAPI {
|
|||
*/
|
||||
getStream(streamId: string): Stream | null;
|
||||
|
||||
/**
|
||||
* Get the currently active stream
|
||||
* @returns The active stream if set, null otherwise
|
||||
*/
|
||||
getActiveStream(): Stream | null;
|
||||
|
||||
|
||||
/**
|
||||
* Get streams that match a specific folder path
|
||||
|
|
@ -64,12 +60,7 @@ export interface StreamsAPI {
|
|||
*/
|
||||
getVersion(): PluginVersion;
|
||||
|
||||
/**
|
||||
* Update the stream bar to match an opened file
|
||||
* @param filePath The path of the file that was opened
|
||||
* @returns True if successful, false if stream not found or update failed
|
||||
*/
|
||||
updateStreamBarFromFile(filePath: string): Promise<boolean>;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { StreamsSettings } from '../../shared/types';
|
|||
*/
|
||||
export class ComponentEventSubscriptionManager {
|
||||
private unsubscribeDateChanged: (() => void) | null = null;
|
||||
private unsubscribeActiveStreamChanged: (() => void) | null = null;
|
||||
|
||||
private unsubscribeSettingsChanged: (() => void) | null = null;
|
||||
private dateStateManager: DateStateManager;
|
||||
|
||||
|
|
@ -58,10 +58,7 @@ export class ComponentEventSubscriptionManager {
|
|||
this.unsubscribeDateChanged = null;
|
||||
}
|
||||
|
||||
if (this.unsubscribeActiveStreamChanged) {
|
||||
this.unsubscribeActiveStreamChanged();
|
||||
this.unsubscribeActiveStreamChanged = null;
|
||||
}
|
||||
|
||||
|
||||
if (this.unsubscribeSettingsChanged) {
|
||||
this.unsubscribeSettingsChanged();
|
||||
|
|
|
|||
|
|
@ -175,9 +175,8 @@ export class ComponentLifecycleManager {
|
|||
// Update component locally
|
||||
component.updateActiveStream(stream);
|
||||
|
||||
// Update global settings silently to ensure persistence without affecting other panels
|
||||
// We use serviceRegistry.streamManagement directly because StreamsAPI doesn't expose suppressEvent
|
||||
await serviceRegistry.streamManagement?.setActiveStream(stream.id, true, true);
|
||||
// Update global settings silently - REMOVED
|
||||
// await serviceRegistry.streamManagement?.setActiveStream(stream.id, true, true);
|
||||
|
||||
centralizedLogger.debug(`Updated stream bar for file: ${filePath}`);
|
||||
}
|
||||
|
|
@ -210,11 +209,31 @@ export class ComponentLifecycleManager {
|
|||
}
|
||||
|
||||
// Get active stream or default stream
|
||||
const streamToUse = this.getStreamToUse();
|
||||
let streamToUse = this.getStreamToUse();
|
||||
|
||||
// If this is a CreateFileView, try to get the stream from the view state
|
||||
if (viewType === configurationService.getViewConfig().CREATE_FILE_VIEW_TYPE) {
|
||||
const view = leaf.view as any;
|
||||
if (view.getState) {
|
||||
const state = view.getState();
|
||||
if (state && state.stream) {
|
||||
streamToUse = state.stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!streamToUse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if component already exists
|
||||
const existingComponent = this.getComponentForLeaf(leaf);
|
||||
if (existingComponent) {
|
||||
// Do NOT force update to streamToUse (which might be default)
|
||||
// Let the component handle its own context via updateStreamContext
|
||||
return;
|
||||
}
|
||||
|
||||
// Create calendar component for this leaf
|
||||
this.createComponentForLeaf(leaf, streamToUse);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,12 +96,26 @@ export class DateNavigationService {
|
|||
*/
|
||||
async selectDate(monthView: Date, day: number): Promise<void> {
|
||||
const selectedDate = new Date(monthView.getFullYear(), monthView.getMonth(), day);
|
||||
console.log(`[DateNavigationService] Selecting date: ${selectedDate.toDateString()} for stream: ${this.stream.name} (${this.stream.id})`);
|
||||
|
||||
// Update the date state
|
||||
this.dateStateManager.setCurrentDate(selectedDate);
|
||||
|
||||
// Navigate to the selected date
|
||||
const command = new OpenStreamDateCommand(this.app, this.stream, selectedDate, this.reuseCurrentTab, this.targetLeaf, this.dateStateManager);
|
||||
await this.navigateToDate(selectedDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific date
|
||||
* @param date - The date to navigate to
|
||||
*/
|
||||
async navigateToDate(date: Date): Promise<void> {
|
||||
console.log(`[DateNavigationService] Navigating to date: ${date.toDateString()} for stream: ${this.stream.name}`);
|
||||
|
||||
// Update the date state
|
||||
this.dateStateManager.setCurrentDate(date);
|
||||
|
||||
const command = new OpenStreamDateCommand(this.app, this.stream, date, this.reuseCurrentTab, this.targetLeaf, this.dateStateManager);
|
||||
await command.execute();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,6 @@ export class EventHandlerService {
|
|||
eventBus.subscribe(EVENTS.STREAM_ADDED, () => this.handleStreamChange());
|
||||
eventBus.subscribe(EVENTS.STREAM_UPDATED, () => this.handleStreamChange());
|
||||
eventBus.subscribe(EVENTS.STREAM_REMOVED, () => this.handleStreamChange());
|
||||
eventBus.subscribe(EVENTS.ACTIVE_STREAM_CHANGED, () => this.handleStreamChange());
|
||||
|
||||
// Listen for create file view opened
|
||||
eventBus.subscribe('create-file-view-opened', (event) => {
|
||||
|
|
|
|||
|
|
@ -59,28 +59,22 @@ export class StreamDataService implements StreamProvider, FilePathProvider {
|
|||
|
||||
/**
|
||||
* Get the currently active stream
|
||||
* @deprecated Global active stream is removed. Returns undefined.
|
||||
*/
|
||||
getActiveStream(): Stream | undefined {
|
||||
const settingsManager = this.getSettingsManager();
|
||||
const activeStreamId = settingsManager.settings?.activeStreamId;
|
||||
if (!activeStreamId) return undefined;
|
||||
const streams = this.getStreams();
|
||||
return streams.find(s => s.id === activeStreamId);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stream to use (active stream or default)
|
||||
* Get stream to use (default stream)
|
||||
*/
|
||||
getStreamToUse(): Stream | undefined {
|
||||
let streamToUse = this.getActiveStream();
|
||||
if (!streamToUse) {
|
||||
// If no active stream, try to get the first available stream
|
||||
const streams = this.getStreams();
|
||||
if (streams && streams.length > 0) {
|
||||
streamToUse = streams[0];
|
||||
}
|
||||
// Always try to get the first available stream if no context is provided
|
||||
const streams = this.getStreams();
|
||||
if (streams && streams.length > 0) {
|
||||
return streams[0];
|
||||
}
|
||||
return streamToUse;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { centralizedLogger } from '../../shared/CentralizedLogger';
|
|||
|
||||
interface PluginInterface {
|
||||
settings: {
|
||||
activeStreamId?: string;
|
||||
};
|
||||
setActiveStream(streamId: string, force?: boolean, suppressEvent?: boolean): Promise<void>;
|
||||
}
|
||||
|
|
@ -125,20 +124,17 @@ export class StreamSelector extends Component {
|
|||
* Select a stream and navigate to it
|
||||
*/
|
||||
private async selectStream(stream: Stream): Promise<void> {
|
||||
// Update the plugin's active stream - this will trigger the event listener
|
||||
if (this.plugin) {
|
||||
await this.plugin.setActiveStream(stream.id, true, true);
|
||||
}
|
||||
// Update the plugin's active stream - REMOVED
|
||||
// if (this.plugin) {
|
||||
// await this.plugin.setActiveStream(stream.id, true, true);
|
||||
// }
|
||||
|
||||
// Notify parent component
|
||||
if (this.onStreamSelected) {
|
||||
this.onStreamSelected(stream);
|
||||
} else {
|
||||
this.navigateToStreamDailyNote(stream);
|
||||
}
|
||||
|
||||
// Hide the dropdown after selection
|
||||
this.hide();
|
||||
|
||||
this.navigateToStreamDailyNote(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { App, WorkspaceLeaf, TFile, MarkdownView, Component, Plugin } from 'obsidian';
|
||||
import { Stream, StreamsSettings } from '../../shared/types';
|
||||
import { DateState } from '../../shared/DateStateManager';
|
||||
import { DateState, DateStateManager } from '../../shared/DateStateManager';
|
||||
import { centralizedLogger } from '../../shared/CentralizedLogger';
|
||||
import { StreamsPluginInterface } from '../../shared/interfaces';
|
||||
import { CREATE_FILE_VIEW_TYPE } from '../file-operations/CreateFileView';
|
||||
import { DateStateManager } from '../../shared/DateStateManager';
|
||||
import { MeldDetectionService } from '../meld-integration';
|
||||
import { configurationService } from '../../shared/ConfigurationService';
|
||||
import { StreamContextService } from '../../shared/StreamContextService';
|
||||
import { CalendarRenderer } from './CalendarRenderer';
|
||||
import { StreamSelector } from './StreamSelector';
|
||||
import { ContentIndicatorService } from './ContentIndicatorService';
|
||||
|
|
@ -25,7 +25,7 @@ interface PluginInterface {
|
|||
barStyle?: 'default' | 'modern';
|
||||
};
|
||||
saveSettings(): void;
|
||||
setActiveStream(streamId: string, force?: boolean): Promise<void>;
|
||||
setActiveStream(streamId: string, force?: boolean, suppressEvent?: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export class StreamsBarComponent extends Component {
|
||||
|
|
@ -63,6 +63,12 @@ export class StreamsBarComponent extends Component {
|
|||
private prevButton: HTMLElement | null = null;
|
||||
private nextButton: HTMLElement | null = null;
|
||||
|
||||
private streamContextService: StreamContextService;
|
||||
|
||||
public get activeStreamId(): string {
|
||||
return this.selectedStream?.id || '';
|
||||
}
|
||||
|
||||
public updateReuseCurrentTab(reuseCurrentTab: boolean): void {
|
||||
this.reuseCurrentTab = reuseCurrentTab;
|
||||
}
|
||||
|
|
@ -72,11 +78,12 @@ export class StreamsBarComponent extends Component {
|
|||
|
||||
this.leaf = leaf;
|
||||
|
||||
this.selectedStream = stream;
|
||||
// this.selectedStream = stream; // Now derived from context
|
||||
this.app = app;
|
||||
this.reuseCurrentTab = reuseCurrentTab;
|
||||
this.streams = streams;
|
||||
this.plugin = plugin;
|
||||
this.streamContextService = new StreamContextService(); // Instantiate service
|
||||
this.dateStateManager = new DateStateManager();
|
||||
|
||||
this.meldDetectionService = new MeldDetectionService();
|
||||
|
|
@ -87,6 +94,9 @@ export class StreamsBarComponent extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
// Initialize with default or passed stream, but immediately verify context
|
||||
this.selectedStream = stream;
|
||||
|
||||
this.contentIndicatorService = new ContentIndicatorService(app, stream, this.meldDetectionService);
|
||||
this.dateNavigationService = new DateNavigationService(app, stream, reuseCurrentTab, this.dateStateManager, this.leaf);
|
||||
this.eventRegistry = new EventHandlerRegistry();
|
||||
|
|
@ -104,8 +114,6 @@ export class StreamsBarComponent extends Component {
|
|||
this.handleDateStateChange(state);
|
||||
});
|
||||
|
||||
|
||||
|
||||
this.eventSubscriptionManager.subscribeToSettingsChanges((settings) => {
|
||||
this.handleSettingsChange(settings);
|
||||
});
|
||||
|
|
@ -126,13 +134,68 @@ export class StreamsBarComponent extends Component {
|
|||
this.registerEvent(this.app.vault.on('modify', this.fileModifyHandler));
|
||||
|
||||
this.registerEvent(this.app.workspace.on('file-open', (file) => {
|
||||
// console.log(`[StreamsBarComponent] file-open event: ${file?.path}`); // Remove debug logs after fix? Or keep for verification? I'll comment them out for now to reduce noise if it works.
|
||||
|
||||
if (this.leaf.view instanceof MarkdownView && this.leaf.view.file === file) {
|
||||
this.updateTodayButton();
|
||||
// console.log(`[StreamsBarComponent] MarkdownView match. Updating context.`);
|
||||
this.updateStreamContext(file);
|
||||
} else {
|
||||
// Handle Custom Views (CreateFileView, InstallMeldView, etc.)
|
||||
const viewType = this.leaf.view.getViewType();
|
||||
// console.log(`[StreamsBarComponent] Checking custom view type: ${viewType}`);
|
||||
|
||||
if (viewType === CREATE_FILE_VIEW_TYPE || viewType === 'streams-create-file-view-encrypted') {
|
||||
const view = this.leaf.view as any;
|
||||
if (view.getState) {
|
||||
const state = view.getState();
|
||||
if (state && state.stream) {
|
||||
// console.log(`[StreamsBarComponent] Custom view stream found: ${state.stream.name}`);
|
||||
this.updateActiveStream(state.stream);
|
||||
this.component.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize component UI first so elements exist
|
||||
this.initializeComponent();
|
||||
|
||||
// Initial context check
|
||||
const viewType = this.leaf.view.getViewType();
|
||||
if (this.leaf.view instanceof MarkdownView) {
|
||||
this.updateStreamContext(this.leaf.view.file);
|
||||
} else if (viewType === CREATE_FILE_VIEW_TYPE || viewType === 'streams-create-file-view-encrypted') {
|
||||
const view = this.leaf.view as any;
|
||||
if (view.getState) {
|
||||
const state = view.getState();
|
||||
if (state && state.stream) {
|
||||
this.updateActiveStream(state.stream);
|
||||
this.component.show(); // Ensure we show it if we have a stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateTodayButton();
|
||||
|
||||
// Ensure initial visibility state is correct
|
||||
if (this.leaf.view instanceof MarkdownView) {
|
||||
this.updateStreamContext(this.leaf.view.file);
|
||||
}
|
||||
}
|
||||
|
||||
private updateStreamContext(file: TFile | null): void {
|
||||
const stream = this.streamContextService.getStreamForFile(file, this.streams);
|
||||
|
||||
if (stream) {
|
||||
if (this.selectedStream?.id !== stream.id) {
|
||||
this.updateActiveStream(stream);
|
||||
}
|
||||
this.component.show();
|
||||
} else {
|
||||
// If no stream found for file, hide the component
|
||||
this.component.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private createUIBuilderCallbacks(): ComponentCallbacks {
|
||||
|
|
@ -161,7 +224,7 @@ export class StreamsBarComponent extends Component {
|
|||
return this.expanded;
|
||||
},
|
||||
onStreamSelected: (stream: Stream) => {
|
||||
this.updateActiveStream(stream);
|
||||
this.handleStreamSwitch(stream);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -251,6 +314,8 @@ export class StreamsBarComponent extends Component {
|
|||
}
|
||||
|
||||
private updateTodayButton() {
|
||||
if (!this.todayButton) return;
|
||||
|
||||
// Check if current file is in stream
|
||||
const view = this.leaf.view;
|
||||
let isInStream = true;
|
||||
|
|
@ -398,21 +463,7 @@ export class StreamsBarComponent extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
private handleActiveStreamChange(eventData: { streamId: string }): void {
|
||||
const { streamId } = eventData;
|
||||
|
||||
if (!streamId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newActiveStream = this.streams.find(s => s.id === streamId);
|
||||
if (!newActiveStream) {
|
||||
centralizedLogger.warn(`Active stream changed to unknown stream ID: ${streamId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateActiveStream(newActiveStream);
|
||||
}
|
||||
|
||||
public updateActiveStream(newActiveStream: Stream): void {
|
||||
this.selectedStream = newActiveStream;
|
||||
|
|
@ -441,6 +492,30 @@ export class StreamsBarComponent extends Component {
|
|||
this.updateTodayButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle explicit stream switch by user (navigates context)
|
||||
*/
|
||||
public handleStreamSwitch(newStream: Stream): void {
|
||||
// First update the UI/internal component state
|
||||
this.updateActiveStream(newStream);
|
||||
|
||||
// Then trigger navigation/update for the current view
|
||||
const view = this.leaf.view;
|
||||
const currentDate = this.dateStateManager.getState().currentDate;
|
||||
const viewType = view.getViewType();
|
||||
|
||||
if (viewType === CREATE_FILE_VIEW_TYPE || viewType === 'streams-create-file-view-encrypted') {
|
||||
// For CreateFileView, update state in place
|
||||
const viewAny = view as any;
|
||||
if (viewAny.setState) {
|
||||
viewAny.setState({ stream: newStream, date: currentDate });
|
||||
}
|
||||
} else if (view instanceof MarkdownView) {
|
||||
// For MarkdownView, navigate to the corresponding date in the new stream
|
||||
this.dateNavigationService.navigateToDate(currentDate);
|
||||
}
|
||||
}
|
||||
|
||||
private handleSettingsChange(settings: StreamsSettings): void {
|
||||
this.stateManager.applyBarStyle(this.component);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,21 +22,21 @@ export class ViewContainerService {
|
|||
findContentContainer(leaf: WorkspaceLeaf): HTMLElement | null {
|
||||
const viewType = leaf.view.getViewType();
|
||||
let contentContainer: HTMLElement | null = null;
|
||||
|
||||
|
||||
if (viewType === 'markdown') {
|
||||
const markdownView = leaf.view as MarkdownView;
|
||||
contentContainer = markdownView.contentEl;
|
||||
|
||||
} else if (viewType === CREATE_FILE_VIEW_TYPE ||
|
||||
viewType === INSTALL_MELD_VIEW_TYPE ||
|
||||
viewType === CREATE_FILE_VIEW_ENCRYPTED_TYPE) {
|
||||
|
||||
} else if (viewType === CREATE_FILE_VIEW_TYPE ||
|
||||
viewType === INSTALL_MELD_VIEW_TYPE ||
|
||||
viewType === CREATE_FILE_VIEW_ENCRYPTED_TYPE) {
|
||||
const view = leaf.view as unknown as ViewWithContentEl;
|
||||
if (!view) {
|
||||
centralizedLogger.error(`View is null for viewType: ${viewType}`);
|
||||
return null;
|
||||
}
|
||||
contentContainer = view.contentEl;
|
||||
|
||||
|
||||
} else if (viewType === 'empty') {
|
||||
// For empty views, try to find the view-content element
|
||||
const viewContent = leaf.view.containerEl.querySelector('.view-content');
|
||||
|
|
@ -48,9 +48,9 @@ export class ViewContainerService {
|
|||
}
|
||||
} else if (viewType === 'file-explorer') {
|
||||
// For file explorer, add to the main content area
|
||||
const mainContent = leaf.view.containerEl.querySelector('.nav-files-container') ||
|
||||
leaf.view.containerEl.querySelector('.nav-files') ||
|
||||
leaf.view.containerEl;
|
||||
const mainContent = leaf.view.containerEl.querySelector('.nav-files-container') ||
|
||||
leaf.view.containerEl.querySelector('.nav-files') ||
|
||||
leaf.view.containerEl;
|
||||
contentContainer = mainContent as HTMLElement;
|
||||
|
||||
} else {
|
||||
|
|
@ -61,7 +61,7 @@ export class ViewContainerService {
|
|||
}
|
||||
contentContainer = view.contentEl;
|
||||
}
|
||||
|
||||
|
||||
return contentContainer;
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ export class ViewContainerService {
|
|||
attachComponent(component: HTMLElement, leaf: WorkspaceLeaf, contentContainer: HTMLElement): boolean {
|
||||
// Add class to content container
|
||||
contentContainer.addClass('streams-markdown-view-content');
|
||||
|
||||
|
||||
// Only add the calendar component if we're in the main editor area
|
||||
if (!this.isMainEditorLeaf(leaf)) {
|
||||
// Don't add calendar component to sidebars or other panes
|
||||
|
|
@ -108,13 +108,13 @@ export class ViewContainerService {
|
|||
|
||||
// Apply standard calendar component styling
|
||||
component.addClass('streams-bar-component');
|
||||
|
||||
|
||||
// Attach directly to the leaf's container element to ensure it stays with the specific editor window
|
||||
const leafContainer = leaf.view.containerEl;
|
||||
|
||||
|
||||
// Find the view-header within this specific leaf
|
||||
const viewHeader = leafContainer.querySelector('.view-header');
|
||||
|
||||
|
||||
if (viewHeader && viewHeader.parentElement) {
|
||||
// Insert after the view-header for this specific leaf
|
||||
viewHeader.parentElement.insertBefore(component, viewHeader.nextSibling);
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ jest.mock('../StreamProviderService');
|
|||
jest.mock('../FilePathProviderService');
|
||||
jest.mock('../../../shared');
|
||||
jest.mock('obsidian', () => ({
|
||||
Component: class MockComponent {},
|
||||
ItemView: class MockItemView {},
|
||||
WorkspaceLeaf: class MockWorkspaceLeaf {},
|
||||
PluginSettingTab: class MockPluginSettingTab {},
|
||||
Modal: class MockModal {}
|
||||
Component: class MockComponent { },
|
||||
ItemView: class MockItemView { },
|
||||
WorkspaceLeaf: class MockWorkspaceLeaf { },
|
||||
PluginSettingTab: class MockPluginSettingTab { },
|
||||
Modal: class MockModal { }
|
||||
}));
|
||||
|
||||
describe('ServiceCoordinator', () => {
|
||||
|
|
@ -51,8 +51,7 @@ describe('ServiceCoordinator', () => {
|
|||
|
||||
mockSettingsManager = {
|
||||
settings: {
|
||||
streams: [],
|
||||
activeStreamId: 'test-stream'
|
||||
streams: []
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -149,7 +148,6 @@ describe('ServiceCoordinator', () => {
|
|||
it('should update existing components when settings change', () => {
|
||||
const newSettings: StreamsSettings = {
|
||||
streams: [],
|
||||
activeStreamId: 'new-stream',
|
||||
showStreamsBarComponent: true,
|
||||
reuseCurrentTab: false,
|
||||
debugLoggingEnabled: false,
|
||||
|
|
|
|||
|
|
@ -33,14 +33,7 @@ export class CommandRegistrationService extends PluginAwareSliceService implemen
|
|||
|
||||
|
||||
private registerStreamCommands(plugin: any): void {
|
||||
// Debug command for updateStreamBarFromFile functionality
|
||||
plugin.addCommand({
|
||||
id: 'debug-update-stream-bar',
|
||||
name: 'Debug: Update Stream Bar from File',
|
||||
callback: async () => {
|
||||
await this.testUpdateStreamBarFromFile();
|
||||
}
|
||||
});
|
||||
// Debug commands removed as updateStreamBarFromFile is deprecated
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -53,43 +46,6 @@ export class CommandRegistrationService extends PluginAwareSliceService implemen
|
|||
return serviceContainer.sliceContainer?.get(serviceName);
|
||||
}
|
||||
|
||||
private async testUpdateStreamBarFromFile(): Promise<void> {
|
||||
const logProvider = this.getLogProvider();
|
||||
const apiService = this.getService('api') as StreamsAPI | undefined;
|
||||
|
||||
if (!apiService) {
|
||||
logProvider.log?.error('API service not available for testing');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the first available stream for testing
|
||||
const streams = apiService.getStreams();
|
||||
if (streams.length === 0) {
|
||||
logProvider.log?.warn('No streams available for testing');
|
||||
return;
|
||||
}
|
||||
|
||||
const testStream = streams[0];
|
||||
const testFilePath = `${testStream.folder}/2024-01-15.md`; // Example file path
|
||||
|
||||
logProvider.log?.info(`Testing updateStreamBarFromFile for file: ${testFilePath}`);
|
||||
logProvider.log?.info(`Expected stream: ${testStream.name} (${testStream.id})`);
|
||||
|
||||
// Test the updateStreamBarFromFile method
|
||||
const result = await apiService.updateStreamBarFromFile(testFilePath);
|
||||
|
||||
if (result) {
|
||||
logProvider.log?.info('✅ updateStreamBarFromFile test PASSED - Stream bar updated successfully');
|
||||
|
||||
// Verify the update by checking the active stream
|
||||
const activeStream = apiService.getActiveStream();
|
||||
if (activeStream && activeStream.id === testStream.id) {
|
||||
logProvider.log?.info(`✅ Verification PASSED - Stream bar now shows: ${activeStream.name}`);
|
||||
} else {
|
||||
logProvider.log?.warn('⚠️ Verification FAILED - Stream bar not updated correctly');
|
||||
}
|
||||
} else {
|
||||
logProvider.log?.error('❌ updateStreamBarFromFile test FAILED - Method returned false');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,38 +86,50 @@ export class CreateFileView extends ItemView {
|
|||
}
|
||||
|
||||
if (state) {
|
||||
console.log(`[CreateFileView] setState called with stream: ${state.stream?.name}`);
|
||||
const previousStream = this.stream;
|
||||
this.filePath = state.filePath || this.filePath;
|
||||
|
||||
// Update properties
|
||||
this.stream = state.stream || this.stream;
|
||||
|
||||
// If the stream changed, update the active stream
|
||||
if (state.stream && state.stream.id !== previousStream.id) {
|
||||
await this.setActiveStream();
|
||||
|
||||
|
||||
// Recalculate file path based on current stream and date
|
||||
// This ensures we switch to the correct folder for the new stream
|
||||
const currentDate = this.dateStateManager.getState().currentDate;
|
||||
const fileName = `${this.formatDateToYYYYMMDD(currentDate)}.md`;
|
||||
|
||||
// Construct new path: StreamFolder + / + FileName
|
||||
// Ensure no double slashes
|
||||
const streamFolder = this.stream.folder.replace(/\/$/, '');
|
||||
this.filePath = `${streamFolder}/${fileName}`;
|
||||
|
||||
console.log(`[CreateFileView] New file path: ${this.filePath}`);
|
||||
|
||||
// Refresh the view with new state
|
||||
if (this.contentEl) {
|
||||
console.log(`[CreateFileView] Re-rendering content for stream: ${this.stream.name}`);
|
||||
this.contentEl.empty();
|
||||
this.contentEl.addClass('streams-create-file-container');
|
||||
this.createFileViewContent(this.contentEl);
|
||||
}
|
||||
|
||||
// Handle date parameter
|
||||
// Handle date parameter (triggers handleDateChange)
|
||||
if (state.date) {
|
||||
const date = typeof state.date === 'string' ? new Date(state.date) : state.date;
|
||||
if (!isNaN(date.getTime())) {
|
||||
this.dateStateManager.setCurrentDate(date);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the view with new state
|
||||
if (this.contentEl) {
|
||||
this.contentEl.empty();
|
||||
this.contentEl.addClass('streams-create-file-container');
|
||||
this.createFileViewContent(this.contentEl);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
centralizedLogger.error(`Error in CreateFileView setState:`, error);
|
||||
// Don't rethrow - just log and continue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private handleDateChange(state: DateState): void {
|
||||
console.log(`[CreateFileView] handleDateChange triggered. Updating path and content.`);
|
||||
// Update the file path based on the new date
|
||||
const fileName = `${this.formatDateToYYYYMMDD(state.currentDate)}.md`;
|
||||
const folderPath = this.filePath.substring(0, this.filePath.lastIndexOf('/'));
|
||||
|
|
@ -139,8 +151,8 @@ export class CreateFileView extends ItemView {
|
|||
}
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
// Set this as the active stream in the main plugin
|
||||
await this.setActiveStream();
|
||||
// Set this as the active stream in the main plugin - REMOVED logic
|
||||
// await this.setActiveStream();
|
||||
|
||||
// Set up date change listener
|
||||
this.unsubscribeDateChanged = this.dateStateManager.onDateChanged((state) => {
|
||||
|
|
@ -261,17 +273,5 @@ export class CreateFileView extends ItemView {
|
|||
await this.fileCreationService.createAndOpenFile(this.filePath, this.stream, this.leaf);
|
||||
}
|
||||
|
||||
private async setActiveStream(): Promise<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) {
|
||||
await plugin.setActiveStream(this.stream.id, true);
|
||||
}
|
||||
} catch (error) {
|
||||
centralizedLogger.error('Error setting active stream:', error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { MeldDetectionService } from '../../slices/meld-integration';
|
|||
|
||||
// Interface for the streams plugin
|
||||
interface StreamsPlugin {
|
||||
setActiveStream(streamId: string, force?: boolean): Promise<void>;
|
||||
setActiveStream(streamId: string, force?: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
// Interface for accessing app.plugins
|
||||
|
|
@ -25,16 +25,16 @@ export const CREATE_FILE_VIEW_ENCRYPTED_TYPE = 'streams-create-file-view-encrypt
|
|||
|
||||
export class CreateFileViewEncrypted extends ItemView {
|
||||
navigation = true; // Enable navigation history integration
|
||||
|
||||
|
||||
private filePath: string;
|
||||
private stream: Stream;
|
||||
private dateStateManager: DateStateManager;
|
||||
private unsubscribeDateChanged: (() => void) | null = null;
|
||||
private emptyStateObserver: MutationObserver | null = null;
|
||||
|
||||
|
||||
constructor(
|
||||
leaf: WorkspaceLeaf,
|
||||
app: App,
|
||||
leaf: WorkspaceLeaf,
|
||||
app: App,
|
||||
filePath: string,
|
||||
stream: Stream
|
||||
) {
|
||||
|
|
@ -63,7 +63,7 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
getState(): { stream: Stream; date: string; filePath: string } {
|
||||
const state = this.dateStateManager.getState();
|
||||
const dateISOString = state.currentDate.toISOString();
|
||||
|
||||
|
||||
return {
|
||||
filePath: this.filePath,
|
||||
stream: this.stream,
|
||||
|
|
@ -77,22 +77,22 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
if (!this || !this.contentEl || !this.leaf || this.contentEl === null || this.leaf === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Additional safety check - ensure the view is still attached to the DOM
|
||||
if (!document.contains(this.contentEl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (state) {
|
||||
const previousStream = this.stream;
|
||||
this.filePath = state.filePath || this.filePath;
|
||||
this.stream = state.stream || this.stream;
|
||||
|
||||
|
||||
// If the stream changed, update the active stream
|
||||
if (state.stream && state.stream.id !== previousStream.id) {
|
||||
await this.setActiveStream();
|
||||
// await this.setActiveStream();
|
||||
}
|
||||
|
||||
|
||||
// Handle date parameter
|
||||
if (state.date) {
|
||||
const date = typeof state.date === 'string' ? new Date(state.date) : state.date;
|
||||
|
|
@ -100,7 +100,7 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
this.dateStateManager.setCurrentDate(date);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Refresh the view with new state
|
||||
if (this.contentEl) {
|
||||
this.contentEl.empty();
|
||||
|
|
@ -119,7 +119,7 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
const fileName = `${this.formatDateToYYYYMMDD(state.currentDate)}.md`;
|
||||
const folderPath = this.filePath.substring(0, this.filePath.lastIndexOf('/'));
|
||||
this.filePath = folderPath ? `${folderPath}/${fileName}` : fileName;
|
||||
|
||||
|
||||
// Refresh the view content
|
||||
if (this.contentEl) {
|
||||
this.contentEl.empty();
|
||||
|
|
@ -137,22 +137,23 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
|
||||
async onOpen(): Promise<void> {
|
||||
// Set this as the active stream in the main plugin
|
||||
await this.setActiveStream();
|
||||
|
||||
// Set this as the active stream in the main plugin - REMOVED
|
||||
// await this.setActiveStream();
|
||||
|
||||
// Set up date change listener
|
||||
this.unsubscribeDateChanged = this.dateStateManager.onDateChanged((state) => {
|
||||
this.handleDateChange(state);
|
||||
});
|
||||
|
||||
|
||||
// Trigger streams bar component to be added to this view
|
||||
this.triggerCalendarComponent();
|
||||
|
||||
|
||||
// Prepare our content element
|
||||
this.contentEl.empty();
|
||||
this.contentEl.addClass('streams-create-file-encrypted-container');
|
||||
|
||||
|
||||
// Content element styling is handled by CSS class
|
||||
|
||||
|
||||
// Hide any empty-state elements that might still be present
|
||||
const hideEmptyStates = () => {
|
||||
const emptyStates = this.leaf.view.containerEl.querySelectorAll('.empty-state, .empty-state-container');
|
||||
|
|
@ -161,24 +162,24 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
htmlEl.addClass('streams-empty-state-hidden');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Hide them immediately
|
||||
hideEmptyStates();
|
||||
|
||||
|
||||
// Set up a MutationObserver to hide them if they get recreated
|
||||
const observer = new MutationObserver(() => {
|
||||
hideEmptyStates();
|
||||
});
|
||||
|
||||
|
||||
observer.observe(this.leaf.view.containerEl, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: false
|
||||
});
|
||||
|
||||
|
||||
// Store observer for cleanup
|
||||
this.emptyStateObserver = observer;
|
||||
|
||||
|
||||
// Create our create file view encrypted content
|
||||
this.createFileViewEncryptedContent(this.contentEl);
|
||||
}
|
||||
|
|
@ -189,57 +190,57 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
this.emptyStateObserver.disconnect();
|
||||
this.emptyStateObserver = null;
|
||||
}
|
||||
|
||||
|
||||
// Clean up date change listener
|
||||
if (this.unsubscribeDateChanged) {
|
||||
this.unsubscribeDateChanged();
|
||||
this.unsubscribeDateChanged = null;
|
||||
}
|
||||
|
||||
|
||||
// Clear content and mark as invalid
|
||||
if (this.contentEl) {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
|
||||
// Mark the view as invalid to prevent setState calls
|
||||
this.contentEl = null!;
|
||||
this.leaf = null!;
|
||||
}
|
||||
|
||||
|
||||
private createFileViewEncryptedContent(container: HTMLElement): void {
|
||||
// Create the content box
|
||||
const contentBox = container.createDiv('streams-create-file-encrypted-content');
|
||||
|
||||
|
||||
// Add icon
|
||||
const iconContainer = contentBox.createDiv('streams-create-file-encrypted-icon');
|
||||
setIcon(iconContainer, 'lock');
|
||||
|
||||
|
||||
// Private indicator
|
||||
const privateIndicator = contentBox.createDiv('streams-create-file-encrypted-private-indicator');
|
||||
const privateIcon = privateIndicator.createSpan('streams-create-file-encrypted-private-icon');
|
||||
setIcon(privateIcon, this.stream.icon || 'book');
|
||||
const privateText = privateIndicator.createSpan('streams-create-file-encrypted-private-text');
|
||||
privateText.setText('Private');
|
||||
|
||||
|
||||
// Date display
|
||||
const dateEl = contentBox.createDiv('streams-create-file-encrypted-date');
|
||||
|
||||
|
||||
const state = this.dateStateManager.getState();
|
||||
const formattedDate = this.formatDate(state.currentDate);
|
||||
dateEl.setText(formattedDate);
|
||||
|
||||
|
||||
// Create button
|
||||
const buttonContainer = contentBox.createDiv('streams-create-file-encrypted-button-container');
|
||||
const createButton = buttonContainer.createEl('button', {
|
||||
cls: 'mod-cta streams-create-file-encrypted-button',
|
||||
text: 'Create Encrypted File'
|
||||
});
|
||||
|
||||
|
||||
createButton.addEventListener('click', async () => {
|
||||
await this.createAndOpenFile();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private triggerCalendarComponent(): void {
|
||||
// Trigger the streams bar component to be added to this view
|
||||
try {
|
||||
|
|
@ -250,7 +251,7 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
// Calendar component trigger failed - not critical
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private formatTitleDate(date: Date): string {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
|
|
@ -258,23 +259,23 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private formatDate(date: Date): string {
|
||||
// Formatting date
|
||||
|
||||
|
||||
try {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
} catch (error) {
|
||||
centralizedLogger.error(`Error formatting date: ${error}`);
|
||||
return "Invalid Date";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async createAndOpenFile(): Promise<void> {
|
||||
try {
|
||||
// Get the file operations service to use the strategy pattern
|
||||
|
|
@ -283,7 +284,7 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
centralizedLogger.error('Streams plugin not found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check if Meld is available for encryption
|
||||
const meldDetectionService = new MeldDetectionService();
|
||||
meldDetectionService.setPlugin(plugin as any);
|
||||
|
|
@ -292,14 +293,14 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
new Notice('Meld plugin is required for encryption but is not available.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Create the file normally first (without encryption)
|
||||
const file = await this.createFileNormally();
|
||||
|
||||
|
||||
if (file instanceof TFile) {
|
||||
// Open the file in the current leaf (this will replace CreateFileViewEncrypted)
|
||||
await this.leaf.openFile(file);
|
||||
|
||||
|
||||
// Trigger encryption after the file is opened
|
||||
// Small delay to ensure the file is fully loaded
|
||||
setTimeout(async () => {
|
||||
|
|
@ -310,11 +311,11 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
centralizedLogger.error('Error creating encrypted file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async createFileNormally(): Promise<TFile | null> {
|
||||
try {
|
||||
const folderPath = this.filePath.substring(0, this.filePath.lastIndexOf('/'));
|
||||
|
||||
|
||||
if (folderPath) {
|
||||
try {
|
||||
const folderExists = this.app.vault.getAbstractFileByPath(folderPath);
|
||||
|
|
@ -325,7 +326,7 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
// Using existing folder
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create the file normally (without encryption)
|
||||
const file = await this.app.vault.create(this.filePath, '');
|
||||
return file instanceof TFile ? file : null;
|
||||
|
|
@ -334,12 +335,12 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async triggerEncryption(file: TFile): Promise<void> {
|
||||
try {
|
||||
// Ensure the file is the active file
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
|
||||
|
||||
if (activeFile?.path !== file.path) {
|
||||
// Find a leaf with this file and make it active
|
||||
const fileLeaf = this.app.workspace.getLeavesOfType('markdown')
|
||||
|
|
@ -351,7 +352,7 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (fileLeaf) {
|
||||
this.app.workspace.setActiveLeaf(fileLeaf, { focus: true });
|
||||
} else {
|
||||
|
|
@ -359,14 +360,14 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Small delay to ensure the file is properly active
|
||||
await new Promise(resolve => setTimeout(resolve, configurationService.getTimingConfig().FILE_OPERATION_DELAY));
|
||||
|
||||
|
||||
// Try to execute the Meld encryption command
|
||||
const commands = getCommands(this.app);
|
||||
const command = commands?.['meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note'];
|
||||
|
||||
|
||||
if (command?.callback && typeof command.callback === 'function') {
|
||||
try {
|
||||
await command.callback();
|
||||
|
|
@ -385,18 +386,6 @@ export class CreateFileViewEncrypted extends ItemView {
|
|||
centralizedLogger.error(`Error triggering encryption for file ${file.path}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
private async setActiveStream(): Promise<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) {
|
||||
await plugin.setActiveStream(this.stream.id, true);
|
||||
}
|
||||
} catch (error) {
|
||||
centralizedLogger.error('Error setting active stream:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { App, Notice, WorkspaceLeaf } from 'obsidian';
|
||||
import { App, Notice, WorkspaceLeaf, MarkdownView } from 'obsidian';
|
||||
import { Stream } from '../../shared/types';
|
||||
import { openStreamDate } from './streamUtils';
|
||||
import { Logger } from '../debug-logging/Logger';
|
||||
import { Command, StreamManager } from '../../shared/interfaces';
|
||||
import { DateStateManager } from '../../shared/DateStateManager';
|
||||
import { StreamContextService } from '../../shared/StreamContextService';
|
||||
|
||||
const log = new Logger();
|
||||
|
||||
|
|
@ -28,12 +29,12 @@ export class OpenTodayCurrentStreamCommand implements Command {
|
|||
return;
|
||||
}
|
||||
|
||||
// Get the current stream from the targetStream or centralized active stream tracking
|
||||
// Get the current stream from the targetStream or dynamic resolution
|
||||
const currentStream = this.targetStream || this.findCurrentStream();
|
||||
|
||||
if (!currentStream) {
|
||||
log.debug('No current stream found, cannot open today note');
|
||||
new Notice('No active stream found. Please open a stream view or file to establish stream context.');
|
||||
new Notice('No active stream context found. Please open a file belonging to a stream first.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -42,16 +43,22 @@ export class OpenTodayCurrentStreamCommand implements Command {
|
|||
}
|
||||
|
||||
private findCurrentStream(): Stream | null {
|
||||
// Get the active stream from the main plugin's centralized tracking
|
||||
if (this.plugin) {
|
||||
const activeStream = this.plugin.getActiveStream();
|
||||
if (activeStream) {
|
||||
log.debug(`Found active stream from plugin: ${activeStream.name}`);
|
||||
return activeStream;
|
||||
}
|
||||
const streamContextService = new StreamContextService();
|
||||
|
||||
// 1. Try target leaf if provided
|
||||
if (this.targetLeaf && this.targetLeaf.view instanceof MarkdownView && this.targetLeaf.view.file) {
|
||||
const stream = streamContextService.getStreamForFile(this.targetLeaf.view.file, this.streams);
|
||||
if (stream) return stream;
|
||||
}
|
||||
|
||||
log.debug('No active stream found in plugin settings');
|
||||
// 2. Try active file
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (activeFile) {
|
||||
const stream = streamContextService.getStreamForFile(activeFile, this.streams);
|
||||
if (stream) return stream;
|
||||
}
|
||||
|
||||
log.debug('No stream context found for current context');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { OpenTodayStreamCommand } from '../file-operations/OpenTodayStreamComman
|
|||
import { OpenTodayCurrentStreamCommand } from '../file-operations/OpenTodayCurrentStreamCommand';
|
||||
|
||||
export class RibbonService extends SettingsAwareSliceService {
|
||||
private ribbonIconsByStream: Map<string, {today?: HTMLElement}> = new Map();
|
||||
private ribbonIconsByStream: Map<string, { today?: HTMLElement }> = new Map();
|
||||
private commandsByStreamId: Map<string, string> = new Map();
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
|
|
@ -31,8 +31,8 @@ export class RibbonService extends SettingsAwareSliceService {
|
|||
eventBus.subscribe(EVENTS.STREAM_ADDED, (event) => this.onStreamAdded(event.data));
|
||||
eventBus.subscribe(EVENTS.STREAM_UPDATED, (event) => this.onStreamUpdated(event.data));
|
||||
eventBus.subscribe(EVENTS.STREAM_REMOVED, (event) => this.onStreamRemoved(event.data.streamId));
|
||||
eventBus.subscribe(EVENTS.ACTIVE_STREAM_CHANGED, () => this.updateAllRibbonIcons());
|
||||
|
||||
// eventBus.subscribe(EVENTS.ACTIVE_STREAM_CHANGED, () => this.updateAllRibbonIcons()); - REMOVED
|
||||
|
||||
// Listen for settings changes
|
||||
eventBus.subscribe(EVENTS.SETTINGS_CHANGED, () => this.updateAllRibbonIcons());
|
||||
}
|
||||
|
|
@ -94,11 +94,11 @@ export class RibbonService extends SettingsAwareSliceService {
|
|||
streamIcons = {};
|
||||
this.ribbonIconsByStream.set(stream.id, streamIcons);
|
||||
}
|
||||
|
||||
|
||||
// Only create the icon if it should be visible
|
||||
if (stream.showTodayInRibbon && !streamIcons.today) {
|
||||
this.log(`Creating Today icon for stream ${stream.id}`);
|
||||
|
||||
|
||||
streamIcons.today = this.getPlugin().addRibbonIcon(
|
||||
stream.icon,
|
||||
`Open today for ${stream.name}`,
|
||||
|
|
@ -152,7 +152,7 @@ export class RibbonService extends SettingsAwareSliceService {
|
|||
this.removeStreamCommand(stream.id);
|
||||
|
||||
const commandId = `open-today-${stream.id}`;
|
||||
|
||||
|
||||
this.getPlugin().addCommand({
|
||||
id: commandId,
|
||||
name: `Open today for ${stream.name}`,
|
||||
|
|
|
|||
|
|
@ -7,92 +7,44 @@ import { withErrorHandling, withAsyncErrorHandling, handleError } from '../../sh
|
|||
import { GlobalStreamIndicator } from '../../shared/GlobalStreamIndicator';
|
||||
|
||||
export class StreamManagementService extends SettingsAwareSliceService {
|
||||
private globalIndicator: GlobalStreamIndicator;
|
||||
// private globalIndicator: GlobalStreamIndicator; // Global indicator removed
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
|
||||
this.globalIndicator = new GlobalStreamIndicator();
|
||||
this.globalIndicator.create(() => this.showStreamSelection());
|
||||
// Global indicator removed
|
||||
// this.globalIndicator = new GlobalStreamIndicator();
|
||||
// this.globalIndicator.create(() => this.showStreamSelection());
|
||||
|
||||
this.registerCommands();
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
cleanup(): void {
|
||||
this.globalIndicator?.destroy();
|
||||
// this.globalIndicator?.destroy();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
onStreamAdded(stream: Stream): void {
|
||||
this.globalIndicator.update(this.getActiveStream());
|
||||
eventBus.emit(EVENTS.STREAM_ADDED, stream, 'stream-management');
|
||||
}
|
||||
|
||||
onStreamUpdated(stream: Stream): void {
|
||||
this.globalIndicator.update(this.getActiveStream());
|
||||
eventBus.emit(EVENTS.STREAM_UPDATED, stream, 'stream-management');
|
||||
}
|
||||
|
||||
onStreamRemoved(streamId: string): void {
|
||||
// If the removed stream was active, clear the active stream
|
||||
if (this.getSettings().activeStreamId === streamId) {
|
||||
void this.setActiveStream(undefined);
|
||||
}
|
||||
this.globalIndicator.update(this.getActiveStream());
|
||||
// Active stream check removed
|
||||
eventBus.emit(EVENTS.STREAM_REMOVED, { streamId }, 'stream-management');
|
||||
}
|
||||
|
||||
onActiveStreamChanged(streamId: string | undefined): void {
|
||||
this.globalIndicator.update(this.getActiveStream());
|
||||
}
|
||||
// onActiveStreamChanged removed
|
||||
|
||||
onSettingsChanged(settings: StreamsSettings): void {
|
||||
this.globalIndicator.update(this.getActiveStream());
|
||||
eventBus.emit(EVENTS.SETTINGS_CHANGED, settings, 'stream-management');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active stream
|
||||
*/
|
||||
public setActiveStream = withAsyncErrorHandling(async (streamId: string | undefined, force = false, suppressEvent = false): Promise<void> => {
|
||||
const currentActiveStreamId = this.getSettings().activeStreamId;
|
||||
|
||||
if (currentActiveStreamId === streamId && !force) {
|
||||
return; // No change needed
|
||||
}
|
||||
|
||||
// Update the settings
|
||||
const plugin = this.getPlugin();
|
||||
plugin.settings.activeStreamId = streamId;
|
||||
|
||||
// Save settings
|
||||
await plugin.saveSettings();
|
||||
|
||||
// Log the change
|
||||
if (streamId) {
|
||||
const stream = this.getStreams().find(s => s.id === streamId);
|
||||
this.log(`Active stream changed to: ${stream?.name || 'Unknown'} (${streamId})`);
|
||||
} else {
|
||||
this.log('Active stream cleared');
|
||||
}
|
||||
|
||||
// Update the global indicator
|
||||
this.globalIndicator.update(this.getActiveStream());
|
||||
|
||||
// Emit event for other services
|
||||
if (!suppressEvent) {
|
||||
eventBus.emit(EVENTS.ACTIVE_STREAM_CHANGED, { streamId, previousStreamId: currentActiveStreamId }, 'stream-management');
|
||||
}
|
||||
}, 'stream-management', 'setActiveStream');
|
||||
|
||||
/**
|
||||
* Get the currently active stream
|
||||
*/
|
||||
public getActiveStream(): Stream | null {
|
||||
return super.getActiveStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show stream selection modal
|
||||
*/
|
||||
|
|
@ -101,8 +53,14 @@ export class StreamManagementService extends SettingsAwareSliceService {
|
|||
this.getPlugin().app,
|
||||
this.getStreams(),
|
||||
async (selectedStream) => {
|
||||
// Previously set active stream, now just emits event or navigates?
|
||||
// For now, if someone calls this, maybe they want to open a stream.
|
||||
// But setActiveStream is gone.
|
||||
// We'll leave it empty or trigger navigation if we can contextually.
|
||||
// For now, removing the setActiveStream call.
|
||||
if (selectedStream) {
|
||||
await this.setActiveStream(selectedStream.id, true);
|
||||
// TODO: Decide what 'selecting a stream' does globally if anything.
|
||||
// Maybe nothing. This modal might be obsolete or need repurposing.
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -115,5 +73,4 @@ export class StreamManagementService extends SettingsAwareSliceService {
|
|||
// No commands currently registered
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue