mirror of
https://github.com/bfloydd/streams.git
synced 2026-07-22 05:49:02 +00:00
move out css into styles.css
This commit is contained in:
parent
7bee354418
commit
2ebc0ad6ad
4 changed files with 142 additions and 397 deletions
351
main.ts
351
main.ts
|
|
@ -9,7 +9,7 @@ import { CREATE_FILE_VIEW_TYPE, CreateFileView } from './src/Widgets/CreateFileV
|
|||
import { STREAM_VIEW_TYPE, StreamViewWidget } from './src/Widgets/StreamViewWidget';
|
||||
import { OpenStreamViewCommand } from './src/commands/OpenStreamViewCommand';
|
||||
|
||||
// Add this type for Lucide icon names
|
||||
// Lucide icon names used by the plugin
|
||||
type LucideIcon =
|
||||
| 'alarm-check'
|
||||
| 'album'
|
||||
|
|
@ -42,7 +42,6 @@ type LucideIcon =
|
|||
| 'tag'
|
||||
| 'trash'
|
||||
| 'user'
|
||||
// Add more icons as needed from https://lucide.dev
|
||||
|
||||
export function isValidIcon(icon: string): icon is LucideIcon {
|
||||
return document.querySelector(`[data-icon="${icon}"]`) !== null;
|
||||
|
|
@ -54,7 +53,6 @@ const DEFAULT_SETTINGS: StreamsSettings = {
|
|||
|
||||
export default class StreamsPlugin extends Plugin {
|
||||
settings: StreamsSettings;
|
||||
// Store icons by stream ID and type
|
||||
private ribbonIconsByStream: Map<string, {today?: HTMLElement, view?: HTMLElement}> = new Map();
|
||||
commandsByStreamId: Map<string, string> = new Map();
|
||||
viewCommandsByStreamId: Map<string, string> = new Map();
|
||||
|
|
@ -62,38 +60,27 @@ export default class StreamsPlugin extends Plugin {
|
|||
public log: Logger = new Logger();
|
||||
|
||||
async onload() {
|
||||
this.log.info('Loading Streams plugin...');
|
||||
|
||||
// Load settings and styles
|
||||
await this.loadSettings();
|
||||
this.loadStyles();
|
||||
this.log = new Logger();
|
||||
|
||||
// Register plugin components
|
||||
this.registerPluginViews();
|
||||
|
||||
// Initialize ribbon icons based on settings
|
||||
this.initializeAllRibbonIcons();
|
||||
|
||||
// Initialize commands for streams that have them enabled
|
||||
this.initializeStreamCommands();
|
||||
|
||||
// Log the current state of all streams for debugging
|
||||
this.logInitialState();
|
||||
|
||||
// Add settings tab
|
||||
this.addSettingTab(new StreamsSettingTab(this.app, this));
|
||||
|
||||
// Register remaining components
|
||||
this.registerEventHandlers();
|
||||
this.initializeAllRibbonIcons();
|
||||
this.initializeStreamCommands();
|
||||
this.initializeMobileIntegration();
|
||||
|
||||
this.addSettingTab(new StreamsSettingTab(this.app, this));
|
||||
this.initializeActiveView();
|
||||
|
||||
this.log.info('Streams plugin loaded successfully');
|
||||
this.logInitialState();
|
||||
|
||||
// Style element for dynamic styles
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = 'streams-calendar-styles';
|
||||
document.head.appendChild(styleEl);
|
||||
|
||||
this.log.info('Streams plugin loaded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the initial state of all streams for debugging
|
||||
*/
|
||||
private logInitialState(): void {
|
||||
this.log.debug("=== INITIAL STREAM RIBBON STATES ===");
|
||||
this.settings.streams.forEach(stream => {
|
||||
|
|
@ -102,9 +89,6 @@ export default class StreamsPlugin extends Plugin {
|
|||
this.log.debug("===================================");
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all plugin views
|
||||
*/
|
||||
private registerPluginViews(): void {
|
||||
// Register CreateFileView
|
||||
this.registerView(
|
||||
|
|
@ -133,19 +117,15 @@ export default class StreamsPlugin extends Plugin {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stream view from a leaf's state
|
||||
*/
|
||||
private createStreamViewFromState(leaf: WorkspaceLeaf): StreamViewWidget {
|
||||
const state = leaf.getViewState();
|
||||
const streamId = state?.state?.streamId;
|
||||
let stream = this.settings.streams.find(s => s.id === streamId);
|
||||
|
||||
// Fallback to first stream if none found
|
||||
// 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) {
|
||||
// Create a dummy stream if none exist
|
||||
stream = {
|
||||
id: "default",
|
||||
name: "Default Stream",
|
||||
|
|
@ -166,23 +146,16 @@ export default class StreamsPlugin extends Plugin {
|
|||
return new StreamViewWidget(leaf, this.app, stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all icons for all streams
|
||||
* Creates the icons in hidden state, then shows if enabled
|
||||
*/
|
||||
private initializeAllRibbonIcons(): void {
|
||||
// First, make sure all streams have their icons created (even if hidden)
|
||||
// Create icons for all streams (even if hidden)
|
||||
this.settings.streams.forEach(stream => {
|
||||
this.createStreamIcons(stream);
|
||||
});
|
||||
|
||||
// Then update visibility based on settings
|
||||
// Update visibility based on settings
|
||||
this.updateAllIconVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ribbon icons for a stream (but don't show them yet)
|
||||
*/
|
||||
private createStreamIcons(stream: Stream): void {
|
||||
// Get or create entry for this stream
|
||||
let streamIcons = this.ribbonIconsByStream.get(stream.id);
|
||||
|
|
@ -191,7 +164,7 @@ export default class StreamsPlugin extends Plugin {
|
|||
this.ribbonIconsByStream.set(stream.id, streamIcons);
|
||||
}
|
||||
|
||||
// Create Today icon if not already created
|
||||
// Create Today icon if needed
|
||||
if (!streamIcons.today) {
|
||||
this.log.debug(`Creating Today icon for stream ${stream.id}, initial visibility: ${stream.showTodayInRibbon}`);
|
||||
|
||||
|
|
@ -204,16 +177,15 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
);
|
||||
|
||||
// Hide initially, then update based on settings later
|
||||
// Hide initially, visibility updated later
|
||||
this.updateIconVisibility(streamIcons.today, false);
|
||||
|
||||
// Apply custom border styling if enabled
|
||||
if (stream.showTodayBorder) {
|
||||
this.applyTodayIconStyles(streamIcons.today, stream);
|
||||
}
|
||||
}
|
||||
|
||||
// Create Full Stream View icon if not already created
|
||||
// Create View icon if needed
|
||||
if (!streamIcons.view) {
|
||||
this.log.debug(`Creating View icon for stream ${stream.id}, initial visibility: ${stream.showFullStreamInRibbon}`);
|
||||
|
||||
|
|
@ -226,27 +198,21 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
);
|
||||
|
||||
// Hide initially, then update based on settings later
|
||||
// Hide initially, visibility updated later
|
||||
this.updateIconVisibility(streamIcons.view, false);
|
||||
|
||||
// Apply custom border styling if enabled
|
||||
if (stream.showViewBorder) {
|
||||
this.applyViewIconStyles(streamIcons.view, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply styling to the Today icon based on stream settings
|
||||
*/
|
||||
private applyTodayIconStyles(icon: HTMLElement, stream: Stream): void {
|
||||
if (!icon) return;
|
||||
|
||||
try {
|
||||
// Apply border to the icon container itself instead of the inner SVG
|
||||
icon.classList.add('streams-today-icon-border');
|
||||
|
||||
// Add custom color if specified
|
||||
if (stream.todayBorderColor && stream.todayBorderColor !== 'default') {
|
||||
icon.style.setProperty('--stream-today-border-color', stream.todayBorderColor);
|
||||
}
|
||||
|
|
@ -255,17 +221,12 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply styling to the View icon based on stream settings
|
||||
*/
|
||||
private applyViewIconStyles(icon: HTMLElement, stream: Stream): void {
|
||||
if (!icon) return;
|
||||
|
||||
try {
|
||||
// Apply border to the icon container itself instead of the inner SVG
|
||||
icon.classList.add('streams-view-icon-border');
|
||||
|
||||
// Add custom color if specified
|
||||
if (stream.viewBorderColor && stream.viewBorderColor !== 'default') {
|
||||
icon.style.setProperty('--stream-view-border-color', stream.viewBorderColor);
|
||||
}
|
||||
|
|
@ -274,68 +235,47 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the visibility of all stream icons based on current settings
|
||||
*/
|
||||
private updateAllIconVisibility(): void {
|
||||
this.settings.streams.forEach(stream => {
|
||||
this.updateStreamIconVisibility(stream);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the visibility of icons for a specific stream
|
||||
*/
|
||||
private updateStreamIconVisibility(stream: Stream): void {
|
||||
const streamIcons = this.ribbonIconsByStream.get(stream.id);
|
||||
if (streamIcons) {
|
||||
// Update Today icon visibility
|
||||
if (streamIcons.today) {
|
||||
this.updateIconVisibility(streamIcons.today, stream.showTodayInRibbon);
|
||||
}
|
||||
|
||||
// Update View icon visibility
|
||||
if (streamIcons.view) {
|
||||
this.updateIconVisibility(streamIcons.view, stream.showFullStreamInRibbon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the styles of a stream's Today icon
|
||||
*/
|
||||
public updateStreamTodayIcon(stream: Stream): void {
|
||||
const streamIcons = this.ribbonIconsByStream.get(stream.id);
|
||||
if (streamIcons && streamIcons.today) {
|
||||
// Toggle the border class on the icon container itself
|
||||
streamIcons.today.classList.toggle('streams-today-icon-border', stream.showTodayBorder);
|
||||
|
||||
// Then apply new styling if enabled
|
||||
if (stream.showTodayBorder) {
|
||||
this.applyTodayIconStyles(streamIcons.today, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the styles of a stream's View icon
|
||||
*/
|
||||
public updateStreamViewIcon(stream: Stream): void {
|
||||
const streamIcons = this.ribbonIconsByStream.get(stream.id);
|
||||
if (streamIcons && streamIcons.view) {
|
||||
// Toggle the border class on the icon container itself
|
||||
streamIcons.view.classList.toggle('streams-view-icon-border', stream.showViewBorder);
|
||||
|
||||
// Then apply new styling if enabled
|
||||
if (stream.showViewBorder) {
|
||||
this.applyViewIconStyles(streamIcons.view, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all ribbon icons
|
||||
*/
|
||||
private removeAllRibbonIcons(): void {
|
||||
this.ribbonIconsByStream.forEach((icons) => {
|
||||
if (icons.today) icons.today.detach();
|
||||
|
|
@ -346,20 +286,13 @@ export default class StreamsPlugin extends Plugin {
|
|||
|
||||
onunload() {
|
||||
this.log.info('Unloading Streams plugin');
|
||||
|
||||
this.cleanupResources();
|
||||
|
||||
this.log.info('Streams plugin unloaded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources when plugin is unloaded
|
||||
*/
|
||||
private cleanupResources(): void {
|
||||
// Remove all ribbon icons
|
||||
this.removeAllRibbonIcons();
|
||||
|
||||
// Remove all calendar widgets
|
||||
this.calendarWidgets.forEach(widget => {
|
||||
widget.destroy();
|
||||
});
|
||||
|
|
@ -369,26 +302,21 @@ export default class StreamsPlugin extends Plugin {
|
|||
this.viewCommandsByStreamId.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all event handlers
|
||||
*/
|
||||
private registerEventHandlers(): void {
|
||||
// Register event for active leaf changes
|
||||
// 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.updateCalendarWidget(leaf);
|
||||
} else if (leaf?.view.getViewType() === CREATE_FILE_VIEW_TYPE) {
|
||||
// Also show calendar widget in the create file view
|
||||
this.updateCalendarWidgetForCreateView(leaf);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Listen for CreateFileView state changes and update calendar widget accordingly
|
||||
// Update calendar when create file state changes
|
||||
this.registerEvent(
|
||||
// Use 'on' directly on app.workspace without type checking
|
||||
// @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 widget');
|
||||
|
|
@ -399,11 +327,8 @@ export default class StreamsPlugin extends Plugin {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize mobile-specific features
|
||||
*/
|
||||
private initializeMobileIntegration(): void {
|
||||
// Mobile share handler (for Android)
|
||||
// Add share handler for Android
|
||||
if (Platform.isAndroidApp) {
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('file-menu', (menu, file) => {
|
||||
|
|
@ -422,9 +347,6 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the active view's calendar widget
|
||||
*/
|
||||
private initializeActiveView(): void {
|
||||
const activeLeaf = this.app.workspace.getActiveViewOfType(MarkdownView)?.leaf;
|
||||
if (activeLeaf) {
|
||||
|
|
@ -432,139 +354,13 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and inject CSS styles for the plugin
|
||||
*/
|
||||
private loadStyles(): void {
|
||||
// Add a style element to the document head
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = 'streams-calendar-styles';
|
||||
document.head.appendChild(styleEl);
|
||||
|
||||
styleEl.textContent = `
|
||||
.stream-calendar-widget {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
font-size: 12px;
|
||||
background-color: var(--background-primary);
|
||||
border-radius: 6px 0 0 6px;
|
||||
box-shadow: -2px 2px 8px rgba(0, 0, 0, 0.15);
|
||||
user-select: none;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.stream-calendar-collapsed {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.stream-calendar-today-button {
|
||||
color: var(--text-accent);
|
||||
font-weight: 600;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.today-button-expanded {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.stream-calendar-expanded {
|
||||
padding: 12px;
|
||||
background-color: var(--background-primary);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.calendar-expanded {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* Icon borders */
|
||||
.streams-today-icon-border {
|
||||
border-left: 2px solid var(--stream-today-border-color, var(--text-accent));
|
||||
}
|
||||
|
||||
.streams-view-icon-border {
|
||||
border-left: 2px solid var(--stream-view-border-color, var(--text-success));
|
||||
}
|
||||
|
||||
/* Calendar styles */
|
||||
.stream-calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.stream-calendar-nav {
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.stream-calendar-nav:hover {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.stream-calendar-date {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stream-calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.dot-container {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.content-dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--text-muted);
|
||||
}
|
||||
|
||||
.calendar-day:hover .content-dot {
|
||||
background-color: var(--text-normal);
|
||||
}
|
||||
|
||||
.calendar-day.today .content-dot {
|
||||
background-color: var(--text-accent);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private updateCalendarWidget(leaf: WorkspaceLeaf) {
|
||||
const view = leaf.view as MarkdownView;
|
||||
const filePath = view.file?.path;
|
||||
|
||||
this.log.debug('Updating calendar widget for file:', filePath);
|
||||
|
||||
// Remove existing widget if any
|
||||
// Clean up existing widgets
|
||||
this.calendarWidgets.forEach((widget) => {
|
||||
widget.destroy();
|
||||
});
|
||||
|
|
@ -572,7 +368,7 @@ export default class StreamsPlugin extends Plugin {
|
|||
|
||||
if (!filePath) return;
|
||||
|
||||
// Log all streams first
|
||||
// Log streams for debugging
|
||||
this.log.debug('Available streams:', this.settings.streams.map(s => ({
|
||||
name: s.name,
|
||||
folder: s.folder
|
||||
|
|
@ -580,7 +376,7 @@ export default class StreamsPlugin extends Plugin {
|
|||
|
||||
// Find which stream this file belongs to
|
||||
const stream = this.settings.streams.find(s => {
|
||||
// Normalize both paths to use forward slashes
|
||||
// Normalize paths for comparison
|
||||
const normalizedFilePath = filePath.split(/[/\\]/).filter(Boolean);
|
||||
const normalizedStreamPath = s.folder.split(/[/\\]/).filter(Boolean);
|
||||
|
||||
|
|
@ -608,7 +404,6 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// Add new methods for handling stream link insertion
|
||||
private async showStreamSelectionModal(filePath: string) {
|
||||
const modal = new StreamSelectionModal(this.app, this.settings.streams, async (selectedStream) => {
|
||||
if (selectedStream) {
|
||||
|
|
@ -642,10 +437,8 @@ export default class StreamsPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
// Create the link
|
||||
// Create and append the link
|
||||
const link = this.app.fileManager.generateMarkdownLink(linkFile, streamPath);
|
||||
|
||||
// Append the link to the stream note
|
||||
const content = await this.app.vault.read(file);
|
||||
const newContent = content + (content.length > 0 ? '\n' : '') + link;
|
||||
await this.app.vault.modify(file, newContent);
|
||||
|
|
@ -658,22 +451,20 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// Add new method to handle calendar widget for create file view
|
||||
private updateCalendarWidgetForCreateView(leaf: WorkspaceLeaf) {
|
||||
const view = leaf.view;
|
||||
if (!view) return;
|
||||
|
||||
// Remove existing widget if any
|
||||
// Clean up existing widgets
|
||||
this.calendarWidgets.forEach((widget) => {
|
||||
widget.destroy();
|
||||
});
|
||||
this.calendarWidgets.clear();
|
||||
|
||||
try {
|
||||
// Get the stream and date from the view's state
|
||||
// Get stream and date from view state
|
||||
const state = view.getState();
|
||||
if (state && state.stream && state.date) {
|
||||
// Cast to appropriate types
|
||||
const stream = state.stream as Stream;
|
||||
const dateString = state.date as string;
|
||||
const date = new Date(dateString);
|
||||
|
|
@ -683,33 +474,29 @@ export default class StreamsPlugin extends Plugin {
|
|||
date: date.toISOString()
|
||||
});
|
||||
|
||||
// Create the calendar widget with the correct date
|
||||
// Create the calendar widget
|
||||
const widget = new CalendarWidget(leaf, stream, this.app);
|
||||
|
||||
// Extract date string in YYYY-MM-DD format for currentViewedDate
|
||||
// Set current viewed date
|
||||
const formattedDate = dateString.split('T')[0];
|
||||
|
||||
// Set the currentViewedDate explicitly
|
||||
widget.setCurrentViewedDate(formattedDate);
|
||||
|
||||
const widgetId = 'create-file-view-' + stream.id;
|
||||
this.calendarWidgets.set(widgetId, widget);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error) {
|
||||
this.log.error('Error setting up calendar widget for create view:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new method for adding stream view commands
|
||||
public addStreamViewCommand(stream: Stream) {
|
||||
const streamId = stream.id;
|
||||
|
||||
// First check if the command already exists
|
||||
// Skip if command already exists
|
||||
if (this.viewCommandsByStreamId.has(streamId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add command
|
||||
const commandId = `streams-view-stream-${streamId}`;
|
||||
this.addCommand({
|
||||
id: commandId,
|
||||
|
|
@ -720,43 +507,32 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
});
|
||||
|
||||
// Store command ID for later removal
|
||||
this.viewCommandsByStreamId.set(streamId, commandId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the "View Full Stream" command
|
||||
*/
|
||||
public removeStreamViewCommand(streamId: string) {
|
||||
const commandId = this.viewCommandsByStreamId.get(streamId);
|
||||
if (commandId) {
|
||||
// Remove command from Obsidian
|
||||
this.removeCommand(commandId);
|
||||
this.viewCommandsByStreamId.delete(streamId);
|
||||
this.log.debug(`Removed View Full Stream command for stream ${streamId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely reset and rebuild all ribbon icons based on current settings
|
||||
*/
|
||||
public forceRebuildAllIcons(): void {
|
||||
this.log.debug("=== FORCE REBUILDING ALL RIBBON ICONS ===");
|
||||
|
||||
// First, ensure all icons are created
|
||||
// Ensure all icons are created
|
||||
this.settings.streams.forEach(stream => {
|
||||
this.createStreamIcons(stream);
|
||||
});
|
||||
|
||||
// Then update their visibility
|
||||
// Update visibility
|
||||
this.updateAllIconVisibility();
|
||||
|
||||
this.log.debug("=== FORCE REBUILD COMPLETE ===");
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly toggle a specific icon without affecting any other icons
|
||||
*/
|
||||
public directlyToggleSpecificRibbonIcon(type: 'today' | 'view', stream: Stream, enabled: boolean): void {
|
||||
this.log.debug(`Directly toggling ${type} icon for ${stream.id} to ${enabled}`);
|
||||
|
||||
|
|
@ -777,22 +553,19 @@ export default class StreamsPlugin extends Plugin {
|
|||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
|
||||
// Ensure backward compatibility by adding viewIcon to any existing streams
|
||||
// Migrate existing streams
|
||||
this.settings.streams.forEach(stream => {
|
||||
this.migrateStreamSettings(stream);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate stream settings to ensure backward compatibility and set defaults
|
||||
*/
|
||||
private migrateStreamSettings(stream: Stream): void {
|
||||
// Add viewIcon if missing
|
||||
if (!stream.viewIcon) {
|
||||
stream.viewIcon = stream.icon;
|
||||
}
|
||||
|
||||
// Migrate from old property names to new ones
|
||||
// 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
|
||||
|
|
@ -809,7 +582,7 @@ export default class StreamsPlugin extends Plugin {
|
|||
delete stream['showViewInRibbon'];
|
||||
}
|
||||
|
||||
// Set defaults if values are undefined
|
||||
// Set default values if undefined
|
||||
if (stream.showTodayInRibbon === undefined) {
|
||||
stream.showTodayInRibbon = false;
|
||||
}
|
||||
|
|
@ -818,7 +591,6 @@ export default class StreamsPlugin extends Plugin {
|
|||
stream.showFullStreamInRibbon = false;
|
||||
}
|
||||
|
||||
// Set defaults for new styling options
|
||||
if (stream.showTodayBorder === undefined) {
|
||||
stream.showTodayBorder = true;
|
||||
}
|
||||
|
|
@ -836,28 +608,19 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save settings
|
||||
*/
|
||||
async saveSettings(refreshUI: boolean = false) {
|
||||
this.log.debug("Saving settings...");
|
||||
try {
|
||||
await this.saveData(this.settings);
|
||||
this.log.debug("Settings saved successfully");
|
||||
|
||||
// Log the currently saved state for debugging
|
||||
this.logSavedSettings();
|
||||
|
||||
// Update visibility based on current settings
|
||||
this.updateAllIconVisibility();
|
||||
} catch (error) {
|
||||
this.log.error("Error saving settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the saved state of all streams for debugging
|
||||
*/
|
||||
private logSavedSettings(): void {
|
||||
this.log.debug("=== SAVED STREAM STATES ===");
|
||||
this.settings.streams.forEach(stream => {
|
||||
|
|
@ -866,14 +629,11 @@ export default class StreamsPlugin extends Plugin {
|
|||
this.log.debug("==========================");
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle commands
|
||||
*/
|
||||
public toggleStreamCommand(stream: Stream) {
|
||||
// Remove existing command first
|
||||
this.removeStreamCommand(stream.id);
|
||||
|
||||
// Only add if enabled
|
||||
// Add if enabled
|
||||
if (stream.addCommand) {
|
||||
this.addStreamCommand(stream);
|
||||
}
|
||||
|
|
@ -885,7 +645,7 @@ export default class StreamsPlugin extends Plugin {
|
|||
// Remove existing view command first
|
||||
this.removeStreamViewCommand(stream.id);
|
||||
|
||||
// Only add if enabled
|
||||
// Add if enabled
|
||||
if (stream.addViewCommand) {
|
||||
this.addStreamViewCommand(stream);
|
||||
}
|
||||
|
|
@ -909,43 +669,31 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
});
|
||||
|
||||
// Store command ID for later removal
|
||||
this.commandsByStreamId.set(stream.id, commandId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the "Open Today" command
|
||||
*/
|
||||
public removeStreamCommand(streamId: string) {
|
||||
const commandId = this.commandsByStreamId.get(streamId);
|
||||
if (commandId) {
|
||||
// Remove command from Obsidian
|
||||
this.removeCommand(commandId);
|
||||
this.commandsByStreamId.delete(streamId);
|
||||
this.log.debug(`Removed Open Today command for stream ${streamId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an icon's visibility with multiple approaches to ensure it sticks
|
||||
*/
|
||||
private updateIconVisibility(icon: HTMLElement, visible: boolean): void {
|
||||
// Log before and after state in a single message to reduce noise
|
||||
// Log visibility state
|
||||
const wasVisible = icon.style.display !== 'none' && !icon.classList.contains('is-hidden');
|
||||
|
||||
// Capture the stream ID and icon type for later reapplying styles
|
||||
// Get stream info for styling
|
||||
const streamId = icon.getAttribute('data-stream-id');
|
||||
const iconType = icon.getAttribute('data-icon-type');
|
||||
|
||||
if (visible) {
|
||||
// Show the icon
|
||||
icon.classList.remove('is-hidden');
|
||||
|
||||
// Preserve custom styling by not completely resetting inline styles
|
||||
// Only change display property
|
||||
icon.style.display = 'flex';
|
||||
|
||||
// Ensure it's in the DOM
|
||||
// Add to DOM if needed
|
||||
if (!document.body.contains(icon)) {
|
||||
const ribbon = this.app.workspace.containerEl.querySelector(".side-dock-ribbon");
|
||||
if (ribbon) {
|
||||
|
|
@ -953,7 +701,7 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// Reapply custom styling if needed
|
||||
// Reapply styling
|
||||
if (streamId) {
|
||||
const stream = this.settings.streams.find(s => s.id === streamId);
|
||||
if (stream) {
|
||||
|
|
@ -965,30 +713,25 @@ export default class StreamsPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Completely hide the icon
|
||||
icon.style.display = 'none';
|
||||
icon.classList.add('is-hidden');
|
||||
}
|
||||
|
||||
// Log visibility change once instead of before and after
|
||||
const isNowVisible = icon.style.display !== 'none' && !icon.classList.contains('is-hidden');
|
||||
this.log.debug(`Icon visibility update: ${wasVisible ? 'visible' : 'hidden'} → ${isNowVisible ? 'visible' : 'hidden'}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize commands for streams that have them enabled
|
||||
*/
|
||||
public initializeStreamCommands(): void {
|
||||
this.log.debug('Initializing stream commands...');
|
||||
|
||||
this.settings.streams.forEach(stream => {
|
||||
// Initialize "Open Today" command if enabled
|
||||
// Add "Open Today" command if enabled
|
||||
if (stream.addCommand) {
|
||||
this.addStreamCommand(stream);
|
||||
this.log.debug(`Added Open Today command for stream ${stream.name}`);
|
||||
}
|
||||
|
||||
// Initialize "View Full Stream" command if enabled
|
||||
// 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}`);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ export class CalendarWidget {
|
|||
this.app.vault.on('modify', this.fileModifyHandler);
|
||||
|
||||
this.initializeWidget();
|
||||
this.loadStyles();
|
||||
}
|
||||
|
||||
private handleFileModify(file: TFile) {
|
||||
|
|
@ -241,31 +240,27 @@ export class CalendarWidget {
|
|||
|
||||
private async updateCalendarGrid(grid: HTMLElement) {
|
||||
grid.empty();
|
||||
grid.style.display = 'grid';
|
||||
|
||||
this.log.debug(`Updating calendar grid for ${this.formatMonthYear(this.currentDate)}`);
|
||||
const daysInMonth = this.getDaysInMonth(this.currentDate.getFullYear(), this.currentDate.getMonth());
|
||||
const firstDayOfMonth = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1).getDay();
|
||||
|
||||
const days = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
||||
days.forEach(day => {
|
||||
const dayHeader = grid.createDiv('calendar-day-header');
|
||||
dayHeader.setText(day);
|
||||
});
|
||||
|
||||
const firstDay = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1);
|
||||
const lastDay = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1, 0);
|
||||
const totalDays = lastDay.getDate();
|
||||
const startingDay = firstDay.getDay();
|
||||
// Add day headers (Su, Mo, Tu, etc.)
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const dayHeader = grid.createDiv('stream-calendar-day-header');
|
||||
dayHeader.textContent = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'][i];
|
||||
}
|
||||
|
||||
this.log.debug(`Month: ${this.currentDate.getMonth() + 1}, First day: ${startingDay}, Total days: ${totalDays}`);
|
||||
|
||||
for (let i = 0; i < startingDay; i++) {
|
||||
// Add empty cells for days before the 1st of the month
|
||||
for (let i = 0; i < firstDayOfMonth; i++) {
|
||||
grid.createDiv('calendar-day empty');
|
||||
}
|
||||
|
||||
for (let day = 1; day <= totalDays; day++) {
|
||||
|
||||
// Add all days of the month
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const dayEl = grid.createDiv('calendar-day');
|
||||
|
||||
const dateContainer = dayEl.createDiv('date-container');
|
||||
dateContainer.setText(String(day));
|
||||
dateContainer.textContent = String(day);
|
||||
|
||||
const dotContainer = dayEl.createDiv('dot-container');
|
||||
|
||||
|
|
@ -331,80 +326,19 @@ export class CalendarWidget {
|
|||
|
||||
private toggleExpanded(collapsedView: HTMLElement, expandedView: HTMLElement) {
|
||||
this.expanded = !this.expanded;
|
||||
expandedView.style.display = this.expanded ? 'block' : 'none';
|
||||
expandedView.toggleClass('calendar-expanded', this.expanded);
|
||||
collapsedView.toggleClass('today-button-expanded', this.expanded);
|
||||
|
||||
if (this.expanded) {
|
||||
expandedView.style.display = 'block';
|
||||
const grid = this.grid;
|
||||
if (grid) {
|
||||
setTimeout(() => {
|
||||
this.updateCalendarGrid(grid);
|
||||
expandedView.toggleClass('calendar-expanded', true);
|
||||
}, 10);
|
||||
}
|
||||
} else {
|
||||
expandedView.toggleClass('calendar-expanded', false);
|
||||
}
|
||||
}
|
||||
|
||||
private loadStyles() {
|
||||
const additionalStyles = `
|
||||
.calendar-day {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border: 1px solid transparent;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.calendar-day.viewed {
|
||||
border-color: var(--text-accent);
|
||||
}
|
||||
|
||||
.dot-container {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.content-dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--text-muted);
|
||||
}
|
||||
|
||||
.calendar-day:hover .content-dot {
|
||||
background-color: var(--text-normal);
|
||||
}
|
||||
|
||||
.calendar-day.today {
|
||||
color: var(--text-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.calendar-day.today .content-dot {
|
||||
background-color: var(--text-accent);
|
||||
}
|
||||
|
||||
.stream-calendar-label {
|
||||
font-size: 0.8em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.stream-calendar-collapsed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const styleEl = document.getElementById('streams-calendar-styles');
|
||||
if (styleEl) {
|
||||
styleEl.textContent += additionalStyles;
|
||||
expandedView.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -465,4 +399,8 @@ export class CalendarWidget {
|
|||
const [year, month, day] = dateString.split('-').map(n => parseInt(n, 10));
|
||||
return new Date(year, month - 1, day);
|
||||
}
|
||||
|
||||
private getDaysInMonth(year: number, month: number): number {
|
||||
return new Date(year, month, 0).getDate();
|
||||
}
|
||||
}
|
||||
|
|
@ -52,10 +52,6 @@ export class StreamViewWidget extends ItemView {
|
|||
|
||||
// Create and position trigger for infinite scroll
|
||||
this.loadMoreTrigger = container.createDiv('stream-view-scroll-trigger');
|
||||
this.loadMoreTrigger.style.width = '100%';
|
||||
this.loadMoreTrigger.style.height = '50px';
|
||||
this.loadMoreTrigger.style.position = 'relative';
|
||||
this.loadMoreTrigger.style.marginTop = '20px';
|
||||
|
||||
this.setupInfiniteScroll();
|
||||
}
|
||||
|
|
@ -240,8 +236,6 @@ export class StreamViewWidget extends ItemView {
|
|||
private addEndMarker(): void {
|
||||
const endMarker = this.streamContentEl.createDiv('stream-view-end-marker');
|
||||
endMarker.textContent = 'End of stream';
|
||||
endMarker.style.opacity = '0';
|
||||
endMarker.style.height = '1px';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -406,10 +400,6 @@ export class StreamViewWidget extends ItemView {
|
|||
await this.loadInitialContent();
|
||||
|
||||
this.loadMoreTrigger = container.createDiv('stream-view-scroll-trigger');
|
||||
this.loadMoreTrigger.style.width = '100%';
|
||||
this.loadMoreTrigger.style.height = '50px';
|
||||
this.loadMoreTrigger.style.position = 'relative';
|
||||
this.loadMoreTrigger.style.marginTop = '20px';
|
||||
|
||||
this.setupInfiniteScroll();
|
||||
|
||||
|
|
|
|||
74
styles.css
74
styles.css
|
|
@ -255,6 +255,78 @@
|
|||
line-height: 1;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border: 1px solid transparent;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.calendar-day.viewed {
|
||||
border-color: var(--text-accent);
|
||||
}
|
||||
|
||||
.dot-container {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.content-dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--text-muted);
|
||||
}
|
||||
|
||||
.calendar-day:hover .content-dot {
|
||||
background-color: var(--text-normal);
|
||||
}
|
||||
|
||||
.calendar-day.today {
|
||||
color: var(--text-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.calendar-day.today .content-dot {
|
||||
background-color: var(--text-accent);
|
||||
}
|
||||
|
||||
.stream-calendar-label {
|
||||
font-size: 0.8em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.streams-today-icon-border {
|
||||
border-left: 2px solid var(--stream-today-border-color, var(--text-accent));
|
||||
}
|
||||
|
||||
.streams-view-icon-border {
|
||||
border-left: 2px solid var(--stream-view-border-color, var(--text-success));
|
||||
}
|
||||
|
||||
.stream-calendar-nav {
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.stream-calendar-nav:hover {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.stream-calendar-date {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.today-button-expanded {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/*********************************************************
|
||||
* CREATE FILE VIEW STYLES
|
||||
*********************************************************/
|
||||
|
|
@ -463,6 +535,7 @@
|
|||
width: 100%;
|
||||
opacity: 0;
|
||||
margin: 20px 0 100px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.stream-view-end-marker {
|
||||
|
|
@ -472,6 +545,7 @@
|
|||
color: var(--text-muted);
|
||||
font-size: 0;
|
||||
opacity: 0;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.stream-view-empty {
|
||||
|
|
|
|||
Loading…
Reference in a new issue