mirror of
https://github.com/bfloydd/streams.git
synced 2026-07-22 05:49:02 +00:00
840 lines
No EOL
25 KiB
TypeScript
840 lines
No EOL
25 KiB
TypeScript
import { App, MarkdownView, Notice, Plugin, WorkspaceLeaf, Platform, TFile } from 'obsidian';
|
|
import { StreamsSettingTab } from './settings';
|
|
import { Stream, StreamsSettings, LucideIcon } from './types';
|
|
import { CalendarComponent } from './src/components/CalendarComponent';
|
|
import { Logger, LogLevel } from './src/utils/Logger';
|
|
import { OpenTodayStreamCommand } from './src/commands/OpenTodayStreamCommand';
|
|
import { StreamSelectionModal } from './src/modals/StreamSelectionModal';
|
|
import { CREATE_FILE_VIEW_TYPE, CreateFileView } from './src/views/CreateFileView';
|
|
import { STREAM_VIEW_TYPE, StreamView } from './src/views/StreamView';
|
|
import { OpenStreamViewCommand } from './src/commands/OpenStreamViewCommand';
|
|
|
|
const DEFAULT_SETTINGS: StreamsSettings = {
|
|
streams: [],
|
|
showCalendarComponent: true
|
|
}
|
|
|
|
export default class StreamsPlugin extends Plugin {
|
|
settings: StreamsSettings;
|
|
private ribbonIconsByStream: Map<string, {today?: HTMLElement, view?: HTMLElement}> = new Map();
|
|
commandsByStreamId: Map<string, string> = new Map();
|
|
viewCommandsByStreamId: Map<string, string> = new Map();
|
|
calendarComponents: Map<string, CalendarComponent> = new Map();
|
|
public log: Logger;
|
|
|
|
async onload() {
|
|
this.log = new Logger('Streams');
|
|
|
|
await this.loadSettings();
|
|
|
|
this.registerPluginViews();
|
|
this.registerEventHandlers();
|
|
this.initializeAllRibbonIcons();
|
|
this.initializeStreamCommands();
|
|
this.initializeMobileIntegration();
|
|
this.registerCalendarCommands();
|
|
this.registerLogCommands();
|
|
|
|
this.addSettingTab(new StreamsSettingTab(this.app, this));
|
|
this.initializeActiveView();
|
|
this.logInitialState();
|
|
|
|
const styleEl = document.createElement('style');
|
|
styleEl.id = 'streams-calendar-styles';
|
|
document.head.appendChild(styleEl);
|
|
|
|
await this.loadStyles();
|
|
|
|
this.log.info('Streams plugin loaded');
|
|
}
|
|
|
|
/**
|
|
* Register commands for controlling logging
|
|
*/
|
|
private registerLogCommands(): void {
|
|
// Add a command to enable/disable logging
|
|
this.addCommand({
|
|
id: 'toggle-logging',
|
|
name: 'Toggle Debug Logging',
|
|
callback: () => {
|
|
// Toggle between DEBUG and NONE
|
|
if (this.log.isEnabled()) {
|
|
this.log.off();
|
|
new Notice('Streams logging disabled');
|
|
} else {
|
|
this.log.on(LogLevel.DEBUG);
|
|
new Notice('Streams logging enabled (DEBUG level)');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
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("===================================");
|
|
}
|
|
|
|
private registerPluginViews(): void {
|
|
// Register CreateFileView
|
|
this.registerView(
|
|
CREATE_FILE_VIEW_TYPE,
|
|
(leaf) => new CreateFileView(leaf, this.app, "", {
|
|
id: "",
|
|
name: "",
|
|
folder: "",
|
|
icon: "calendar",
|
|
viewIcon: "list",
|
|
showTodayInRibbon: false,
|
|
showFullStreamInRibbon: false,
|
|
addCommand: false,
|
|
addViewCommand: false,
|
|
showTodayBorder: true,
|
|
showViewBorder: true,
|
|
todayBorderColor: "var(--text-accent)",
|
|
viewBorderColor: "var(--text-success)"
|
|
}, new Date())
|
|
);
|
|
|
|
// Register StreamView
|
|
this.registerView(
|
|
STREAM_VIEW_TYPE,
|
|
(leaf) => this.createStreamViewFromState(leaf)
|
|
);
|
|
}
|
|
|
|
private createStreamViewFromState(leaf: WorkspaceLeaf): StreamView {
|
|
const state = leaf.getViewState();
|
|
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 (!stream && this.settings.streams.length > 0) {
|
|
stream = this.settings.streams[0];
|
|
} else if (!stream) {
|
|
stream = {
|
|
id: "default",
|
|
name: "Default Stream",
|
|
folder: "",
|
|
icon: "file-text",
|
|
viewIcon: "list",
|
|
showTodayInRibbon: false,
|
|
showFullStreamInRibbon: false,
|
|
addCommand: false,
|
|
addViewCommand: false,
|
|
showTodayBorder: true,
|
|
showViewBorder: true,
|
|
todayBorderColor: "var(--text-accent)",
|
|
viewBorderColor: "var(--text-success)"
|
|
};
|
|
}
|
|
|
|
return new StreamView(leaf, this.app, stream);
|
|
}
|
|
|
|
private initializeAllRibbonIcons(): void {
|
|
// Create icons for all streams (even if hidden)
|
|
this.settings.streams.forEach(stream => {
|
|
this.createStreamIcons(stream);
|
|
});
|
|
|
|
// Update visibility based on settings
|
|
this.updateAllIconVisibility();
|
|
}
|
|
|
|
private createStreamIcons(stream: Stream): void {
|
|
// Get or create entry for this stream
|
|
let streamIcons = this.ribbonIconsByStream.get(stream.id);
|
|
if (!streamIcons) {
|
|
streamIcons = {};
|
|
this.ribbonIconsByStream.set(stream.id, streamIcons);
|
|
}
|
|
|
|
// Create Today icon if needed
|
|
if (!streamIcons.today) {
|
|
this.log.debug(`Creating Today icon for stream ${stream.id}, initial visibility: ${stream.showTodayInRibbon}`);
|
|
|
|
streamIcons.today = this.addRibbonIcon(
|
|
stream.icon,
|
|
`Streams: ${stream.name}, Today`,
|
|
() => {
|
|
const command = new OpenTodayStreamCommand(this.app, stream);
|
|
command.execute();
|
|
}
|
|
);
|
|
|
|
// Hide initially, visibility updated later
|
|
this.updateIconVisibility(streamIcons.today, false);
|
|
|
|
if (stream.showTodayBorder) {
|
|
this.applyTodayIconStyles(streamIcons.today, stream);
|
|
}
|
|
}
|
|
|
|
// Create View icon if needed
|
|
if (!streamIcons.view) {
|
|
this.log.debug(`Creating View icon for stream ${stream.id}, initial visibility: ${stream.showFullStreamInRibbon}`);
|
|
|
|
streamIcons.view = this.addRibbonIcon(
|
|
stream.viewIcon || stream.icon,
|
|
`Streams: ${stream.name}, Full`,
|
|
() => {
|
|
const command = new OpenStreamViewCommand(this.app, stream);
|
|
command.execute();
|
|
}
|
|
);
|
|
|
|
// Hide initially, visibility updated later
|
|
this.updateIconVisibility(streamIcons.view, false);
|
|
|
|
if (stream.showViewBorder) {
|
|
this.applyViewIconStyles(streamIcons.view, stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
private applyTodayIconStyles(icon: HTMLElement, stream: Stream): void {
|
|
if (!icon) return;
|
|
|
|
try {
|
|
icon.classList.add('streams-today-icon-border');
|
|
|
|
if (stream.todayBorderColor && stream.todayBorderColor !== 'default') {
|
|
icon.setAttribute('data-border-color', stream.todayBorderColor);
|
|
}
|
|
} catch (error) {
|
|
this.log.error('Error applying today icon styles:', error);
|
|
}
|
|
}
|
|
|
|
private applyViewIconStyles(icon: HTMLElement, stream: Stream): void {
|
|
if (!icon) return;
|
|
|
|
try {
|
|
icon.classList.add('streams-view-icon-border');
|
|
|
|
if (stream.viewBorderColor && stream.viewBorderColor !== 'default') {
|
|
icon.setAttribute('data-border-color', stream.viewBorderColor);
|
|
}
|
|
} catch (error) {
|
|
this.log.error('Error applying view icon styles:', error);
|
|
}
|
|
}
|
|
|
|
private updateAllIconVisibility(): void {
|
|
this.settings.streams.forEach(stream => {
|
|
this.updateStreamIconVisibility(stream);
|
|
});
|
|
}
|
|
|
|
private updateStreamIconVisibility(stream: Stream): void {
|
|
const streamIcons = this.ribbonIconsByStream.get(stream.id);
|
|
if (streamIcons) {
|
|
if (streamIcons.today) {
|
|
this.updateIconVisibility(streamIcons.today, stream.showTodayInRibbon);
|
|
}
|
|
|
|
if (streamIcons.view) {
|
|
this.updateIconVisibility(streamIcons.view, stream.showFullStreamInRibbon);
|
|
}
|
|
}
|
|
}
|
|
|
|
public updateStreamTodayIcon(stream: Stream): void {
|
|
const streamIcons = this.ribbonIconsByStream.get(stream.id);
|
|
if (streamIcons && streamIcons.today) {
|
|
streamIcons.today.classList.toggle('streams-today-icon-border', stream.showTodayBorder);
|
|
|
|
if (stream.showTodayBorder) {
|
|
this.applyTodayIconStyles(streamIcons.today, stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
public updateStreamViewIcon(stream: Stream): void {
|
|
const streamIcons = this.ribbonIconsByStream.get(stream.id);
|
|
if (streamIcons && streamIcons.view) {
|
|
streamIcons.view.classList.toggle('streams-view-icon-border', stream.showViewBorder);
|
|
|
|
if (stream.showViewBorder) {
|
|
this.applyViewIconStyles(streamIcons.view, stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
private removeAllRibbonIcons(): void {
|
|
this.ribbonIconsByStream.forEach((icons) => {
|
|
if (icons.today) icons.today.detach();
|
|
if (icons.view) icons.view.detach();
|
|
});
|
|
this.ribbonIconsByStream.clear();
|
|
}
|
|
|
|
onunload() {
|
|
this.log.info('Unloading Streams plugin');
|
|
this.cleanupResources();
|
|
this.log.info('Streams plugin unloaded');
|
|
}
|
|
|
|
private cleanupResources(): void {
|
|
this.removeAllRibbonIcons();
|
|
|
|
this.calendarComponents.forEach(component => {
|
|
component.destroy();
|
|
});
|
|
this.calendarComponents.clear();
|
|
|
|
this.commandsByStreamId.clear();
|
|
this.viewCommandsByStreamId.clear();
|
|
}
|
|
|
|
private registerEventHandlers(): void {
|
|
// Handle active leaf changes
|
|
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);
|
|
}
|
|
})
|
|
);
|
|
|
|
// Update calendar when create file state changes
|
|
this.registerEvent(
|
|
// @ts-ignore - Custom event not in type definitions
|
|
this.app.workspace.on('streams-create-file-state-changed', (view: any) => {
|
|
this.log.debug('Create file state changed, updating calendar component');
|
|
if (view && view.leaf) {
|
|
this.updateCalendarComponentForCreateView(view.leaf);
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
private initializeMobileIntegration(): void {
|
|
// Add share handler for Android
|
|
if (Platform.isAndroidApp) {
|
|
this.registerEvent(
|
|
this.app.workspace.on('file-menu', (menu, file) => {
|
|
if (file && this.settings.streams.length > 0) {
|
|
menu.addItem((item) => {
|
|
item
|
|
.setTitle('Insert link into stream')
|
|
.setIcon('link')
|
|
.onClick(async () => {
|
|
await this.showStreamSelectionModal(file.path);
|
|
});
|
|
});
|
|
}
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
private initializeActiveView(): void {
|
|
const activeLeaf = this.app.workspace.getActiveViewOfType(MarkdownView)?.leaf;
|
|
if (activeLeaf) {
|
|
this.updateCalendarComponent(activeLeaf);
|
|
}
|
|
}
|
|
|
|
public updateCalendarComponent(leaf: WorkspaceLeaf) {
|
|
const view = leaf.view as MarkdownView;
|
|
const filePath = view.file?.path;
|
|
|
|
this.log.debug('Updating calendar component for file:', filePath);
|
|
|
|
// Clean up existing components
|
|
this.calendarComponents.forEach((component) => {
|
|
component.destroy();
|
|
});
|
|
this.calendarComponents.clear();
|
|
|
|
// Exit early if there's no file path or the calendar is disabled
|
|
if (!filePath || !this.settings.showCalendarComponent) {
|
|
this.log.debug(`Not creating calendar component. File path: ${filePath ? 'exists' : 'missing'}, Component enabled: ${this.settings.showCalendarComponent}`);
|
|
return;
|
|
}
|
|
|
|
// Log streams for debugging
|
|
this.log.debug(`Looking for stream matching file: ${filePath}`);
|
|
this.log.debug('Available streams:', this.settings.streams.map(s => ({
|
|
name: s.name,
|
|
folder: s.folder
|
|
})));
|
|
|
|
// 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;
|
|
});
|
|
|
|
if (stream) {
|
|
this.log.debug(`File belongs to stream: ${stream.name} (${stream.folder})`);
|
|
const component = new CalendarComponent(leaf, stream, this.app);
|
|
const componentId = filePath || crypto.randomUUID();
|
|
this.calendarComponents.set(componentId, component);
|
|
this.log.debug('Calendar component created successfully');
|
|
} else {
|
|
this.log.debug('File does not belong to any stream');
|
|
}
|
|
}
|
|
|
|
private async showStreamSelectionModal(filePath: string) {
|
|
const modal = new StreamSelectionModal(this.app, this.settings.streams, async (selectedStream) => {
|
|
if (selectedStream) {
|
|
await this.insertLinkIntoStream(selectedStream, filePath);
|
|
}
|
|
});
|
|
modal.open();
|
|
}
|
|
|
|
private async insertLinkIntoStream(stream: Stream, filePath: string) {
|
|
try {
|
|
// Get today's stream note
|
|
const date = new Date();
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const fileName = `${year}-${month}-${day}.md`;
|
|
const streamPath = `${stream.folder}/${fileName}`;
|
|
|
|
// Create or get the stream note
|
|
let file = this.app.vault.getAbstractFileByPath(streamPath);
|
|
if (!file || !(file instanceof TFile)) {
|
|
file = await this.app.vault.create(streamPath, '');
|
|
}
|
|
|
|
if (file instanceof TFile) {
|
|
// Get the file to link to
|
|
const linkFile = this.app.vault.getAbstractFileByPath(filePath);
|
|
if (!linkFile || !(linkFile instanceof TFile)) {
|
|
new Notice('Could not find file to link to');
|
|
return;
|
|
}
|
|
|
|
// Create and append the link
|
|
const link = this.app.fileManager.generateMarkdownLink(linkFile, streamPath);
|
|
const content = await this.app.vault.read(file);
|
|
const newContent = content + (content.length > 0 ? '\n' : '') + link;
|
|
await this.app.vault.modify(file, newContent);
|
|
|
|
new Notice(`Added link to ${stream.name}`);
|
|
}
|
|
} catch (error) {
|
|
this.log.error('Error inserting link:', error);
|
|
new Notice('Failed to insert link into stream');
|
|
}
|
|
}
|
|
|
|
public updateCalendarComponentForCreateView(leaf: WorkspaceLeaf) {
|
|
const view = leaf.view;
|
|
if (!view) {
|
|
this.log.debug('Cannot update calendar component: no view found');
|
|
return;
|
|
}
|
|
|
|
// Clean up existing components
|
|
this.calendarComponents.forEach((component) => {
|
|
component.destroy();
|
|
});
|
|
this.calendarComponents.clear();
|
|
|
|
if (!this.settings.showCalendarComponent) {
|
|
this.log.debug('Calendar component is disabled in settings');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Get stream and date from view state
|
|
const state = view.getState();
|
|
if (!state) {
|
|
this.log.debug('No state found in create file view');
|
|
return;
|
|
}
|
|
|
|
if (!state.stream || !state.date) {
|
|
this.log.debug('Missing stream or date in create file view state', {
|
|
hasStream: !!state.stream,
|
|
hasDate: !!state.date
|
|
});
|
|
return;
|
|
}
|
|
|
|
const stream = state.stream as Stream;
|
|
const dateString = state.date as string;
|
|
const date = new Date(dateString);
|
|
|
|
this.log.debug('Creating calendar component for create file view:', {
|
|
stream: stream.name,
|
|
date: date.toISOString()
|
|
});
|
|
|
|
// Create the calendar component
|
|
const component = new CalendarComponent(leaf, stream, this.app);
|
|
|
|
// Set current viewed date
|
|
const formattedDate = dateString.split('T')[0];
|
|
component.setCurrentViewedDate(formattedDate);
|
|
|
|
const componentId = 'create-file-view-' + stream.id;
|
|
this.calendarComponents.set(componentId, component);
|
|
|
|
this.log.debug('Calendar component for create file view created successfully');
|
|
} catch (error) {
|
|
this.log.error('Error setting up calendar component for create view:', error);
|
|
}
|
|
}
|
|
|
|
public addStreamViewCommand(stream: Stream) {
|
|
const streamId = stream.id;
|
|
|
|
// Skip if command already exists
|
|
if (this.viewCommandsByStreamId.has(streamId)) {
|
|
return;
|
|
}
|
|
|
|
const commandId = `streams-view-${streamId}`;
|
|
this.addCommand({
|
|
id: commandId,
|
|
name: `Open Full View: ${stream.name}`,
|
|
callback: () => {
|
|
const command = new OpenStreamViewCommand(this.app, stream);
|
|
command.execute();
|
|
}
|
|
});
|
|
|
|
this.viewCommandsByStreamId.set(streamId, commandId);
|
|
}
|
|
|
|
public removeStreamViewCommand(streamId: string) {
|
|
const commandId = this.viewCommandsByStreamId.get(streamId);
|
|
if (commandId) {
|
|
this.removeCommand(commandId);
|
|
this.viewCommandsByStreamId.delete(streamId);
|
|
this.log.debug(`Removed View Full Stream command for stream ${streamId}`);
|
|
}
|
|
}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
|
|
// Ensure showCalendarComponent has a default value if not set
|
|
if (this.settings.showCalendarComponent === undefined) {
|
|
this.settings.showCalendarComponent = true;
|
|
}
|
|
|
|
// Migrate existing streams
|
|
this.settings.streams.forEach(stream => {
|
|
this.migrateStreamSettings(stream);
|
|
});
|
|
}
|
|
|
|
private migrateStreamSettings(stream: Stream): void {
|
|
// Add viewIcon if missing
|
|
if (!stream.viewIcon) {
|
|
stream.viewIcon = stream.icon;
|
|
}
|
|
|
|
// Migrate from old property names
|
|
// @ts-ignore - Accessing old property names for migration
|
|
if (stream['showInRibbon'] !== undefined && stream.showTodayInRibbon === undefined) {
|
|
// @ts-ignore - Accessing old property names for migration
|
|
stream.showTodayInRibbon = stream['showInRibbon'];
|
|
// @ts-ignore - Accessing old property names for migration
|
|
delete stream['showInRibbon'];
|
|
}
|
|
|
|
// @ts-ignore - Accessing old property names for migration
|
|
if (stream['showViewInRibbon'] !== undefined && stream.showFullStreamInRibbon === undefined) {
|
|
// @ts-ignore - Accessing old property names for migration
|
|
stream.showFullStreamInRibbon = stream['showViewInRibbon'];
|
|
// @ts-ignore - Accessing old property names for migration
|
|
delete stream['showViewInRibbon'];
|
|
}
|
|
|
|
// Set default values if undefined
|
|
if (stream.showTodayInRibbon === undefined) {
|
|
stream.showTodayInRibbon = false;
|
|
}
|
|
|
|
if (stream.showFullStreamInRibbon === undefined) {
|
|
stream.showFullStreamInRibbon = false;
|
|
}
|
|
|
|
if (stream.showTodayBorder === undefined) {
|
|
stream.showTodayBorder = true;
|
|
}
|
|
|
|
if (stream.showViewBorder === undefined) {
|
|
stream.showViewBorder = true;
|
|
}
|
|
|
|
if (stream.todayBorderColor === undefined) {
|
|
stream.todayBorderColor = 'var(--text-accent)';
|
|
}
|
|
|
|
if (stream.viewBorderColor === undefined) {
|
|
stream.viewBorderColor = 'var(--text-success)';
|
|
}
|
|
}
|
|
|
|
async saveSettings(refreshUI: boolean = false) {
|
|
this.log.debug("Saving settings...");
|
|
try {
|
|
await this.saveData(this.settings);
|
|
this.log.debug("Settings saved successfully");
|
|
|
|
this.logSavedSettings();
|
|
this.updateAllIconVisibility();
|
|
} catch (error) {
|
|
this.log.error("Error saving settings:", error);
|
|
}
|
|
}
|
|
|
|
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("==========================");
|
|
}
|
|
|
|
private loadStyles(): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
this.log.debug("Loading styles from styles.css");
|
|
this.app.vault.adapter.readBinary(
|
|
`${this.app.vault.configDir}/plugins/streams/styles.css`
|
|
).then(data => {
|
|
// Convert ArrayBuffer to string
|
|
const decoder = new TextDecoder('utf-8');
|
|
const cssContent = decoder.decode(data);
|
|
|
|
// Add styles
|
|
const styleEl = document.createElement('style');
|
|
styleEl.textContent = cssContent;
|
|
document.head.appendChild(styleEl);
|
|
resolve();
|
|
}).catch(error => {
|
|
this.log.error("Error reading styles.css:", error);
|
|
reject(error);
|
|
});
|
|
} catch (error) {
|
|
this.log.error("Error loading styles:", error);
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
public toggleStreamCommand(stream: Stream) {
|
|
// Remove existing command first
|
|
this.removeStreamCommand(stream.id);
|
|
|
|
// Add if enabled
|
|
if (stream.addCommand) {
|
|
this.addStreamCommand(stream);
|
|
}
|
|
|
|
this.log.debug(`Toggled Open Today command for stream ${stream.id} to ${stream.addCommand}`);
|
|
}
|
|
|
|
public toggleStreamViewCommand(stream: Stream) {
|
|
// Remove existing view command first
|
|
this.removeStreamViewCommand(stream.id);
|
|
|
|
// Add if enabled
|
|
if (stream.addViewCommand) {
|
|
this.addStreamViewCommand(stream);
|
|
}
|
|
|
|
this.log.debug(`Toggled View Full Stream command for stream ${stream.id} to ${stream.addViewCommand}`);
|
|
}
|
|
|
|
private addStreamCommand(stream: Stream) {
|
|
const commandId = `streams-open-${stream.id}`;
|
|
|
|
// Remove existing command if any
|
|
this.removeStreamCommand(stream.id);
|
|
|
|
// Add new command
|
|
const command = this.addCommand({
|
|
id: commandId,
|
|
name: `${stream.name}, Today`,
|
|
callback: async () => {
|
|
const command = new OpenTodayStreamCommand(this.app, stream);
|
|
await command.execute();
|
|
}
|
|
});
|
|
|
|
this.commandsByStreamId.set(stream.id, commandId);
|
|
}
|
|
|
|
public removeStreamCommand(streamId: string) {
|
|
const commandId = this.commandsByStreamId.get(streamId);
|
|
if (commandId) {
|
|
this.removeCommand(commandId);
|
|
this.commandsByStreamId.delete(streamId);
|
|
this.log.debug(`Removed Open Today command for stream ${streamId}`);
|
|
}
|
|
}
|
|
|
|
private updateIconVisibility(icon: HTMLElement, visible: boolean): void {
|
|
// Log visibility state
|
|
const wasVisible = !icon.classList.contains('stream-icon-hidden') && !icon.classList.contains('is-hidden');
|
|
|
|
// Get stream info for styling
|
|
const streamId = icon.getAttribute('data-stream-id');
|
|
const iconType = icon.getAttribute('data-icon-type');
|
|
|
|
if (visible) {
|
|
icon.classList.remove('is-hidden');
|
|
icon.classList.remove('stream-icon-hidden');
|
|
icon.classList.add('stream-icon-visible');
|
|
|
|
// Add to DOM if needed
|
|
if (!document.body.contains(icon)) {
|
|
const ribbon = this.app.workspace.containerEl.querySelector(".side-dock-ribbon");
|
|
if (ribbon) {
|
|
ribbon.appendChild(icon);
|
|
}
|
|
}
|
|
|
|
// Reapply styling
|
|
if (streamId) {
|
|
const stream = this.settings.streams.find(s => s.id === streamId);
|
|
if (stream) {
|
|
if (iconType === 'today') {
|
|
this.applyTodayIconStyles(icon, stream);
|
|
} else if (iconType === 'view') {
|
|
this.applyViewIconStyles(icon, stream);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
icon.classList.add('stream-icon-hidden');
|
|
icon.classList.add('is-hidden');
|
|
}
|
|
|
|
const isNowVisible = !icon.classList.contains('stream-icon-hidden') && !icon.classList.contains('is-hidden');
|
|
this.log.debug(`Icon visibility update: ${wasVisible ? 'visible' : 'hidden'} → ${isNowVisible ? 'visible' : 'hidden'}`);
|
|
}
|
|
|
|
public initializeStreamCommands(): void {
|
|
this.log.debug('Initializing stream commands...');
|
|
|
|
this.settings.streams.forEach(stream => {
|
|
// Add "Open Today" command if enabled
|
|
if (stream.addCommand) {
|
|
this.addStreamCommand(stream);
|
|
this.log.debug(`Added Open Today command for stream ${stream.name}`);
|
|
}
|
|
|
|
// Add "View Full Stream" command if enabled
|
|
if (stream.addViewCommand) {
|
|
this.addStreamViewCommand(stream);
|
|
this.log.debug(`Added View Full Stream command for stream ${stream.name}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
private registerCalendarCommands(): void {
|
|
this.addCommand({
|
|
id: 'toggle-calendar-component',
|
|
name: 'Toggle Calendar Component',
|
|
callback: () => {
|
|
this.settings.showCalendarComponent = !this.settings.showCalendarComponent;
|
|
this.saveSettings();
|
|
|
|
// Immediately refresh all components
|
|
this.refreshAllCalendarComponents();
|
|
|
|
new Notice(`Calendar component ${this.settings.showCalendarComponent ? 'shown' : 'hidden'}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
public refreshAllCalendarComponents(): void {
|
|
// Always clean up existing components first
|
|
this.log.debug(`Refreshing all calendar components. Current setting: ${this.settings.showCalendarComponent}`);
|
|
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
|
|
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) {
|
|
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.');
|
|
}
|
|
}
|
|
} |