move out css into styles.css

This commit is contained in:
Ben Floyd 2025-05-03 14:16:19 -06:00
parent 7bee354418
commit 2ebc0ad6ad
4 changed files with 142 additions and 397 deletions

351
main.ts
View file

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

View file

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

View file

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

View file

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