mirror of
https://github.com/formax68/memoChron.git
synced 2026-07-22 12:30:31 +00:00
colours in calendars
This commit is contained in:
parent
2ddddbfcdb
commit
6bd6d2f684
7 changed files with 392 additions and 10 deletions
|
|
@ -13,6 +13,8 @@ export interface CalendarEvent {
|
|||
description?: string;
|
||||
location?: string;
|
||||
source: string;
|
||||
sourceId: string; // Unique identifier for the calendar source
|
||||
color?: string; // Color assigned to this event's calendar
|
||||
}
|
||||
|
||||
interface CacheData {
|
||||
|
|
@ -474,6 +476,8 @@ export class CalendarService {
|
|||
description: event.description,
|
||||
location: event.location,
|
||||
source: source.name,
|
||||
sourceId: source.url, // Using URL as unique identifier
|
||||
color: source.color,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -497,6 +501,8 @@ export class CalendarService {
|
|||
description: event.description,
|
||||
location: event.location,
|
||||
source: source.name,
|
||||
sourceId: source.url, // Using URL as unique identifier
|
||||
color: source.color,
|
||||
}];
|
||||
}
|
||||
|
||||
|
|
@ -567,6 +573,8 @@ export class CalendarService {
|
|||
description: exception.description,
|
||||
location: exception.location,
|
||||
source: source.name,
|
||||
sourceId: source.url, // Using URL as unique identifier
|
||||
color: source.color,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ export class IcsImportService {
|
|||
end: endDate,
|
||||
description: event.description,
|
||||
location: event.location,
|
||||
source: "Imported"
|
||||
source: "Imported",
|
||||
sourceId: "imported" // Special source ID for imported events
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from "obsidian";
|
||||
import MemoChron from "../main";
|
||||
import { CalendarSource } from "./types";
|
||||
import { CALENDAR_COLOR_PALETTE } from "../utils/constants";
|
||||
|
||||
export class SettingsTab extends PluginSettingTab {
|
||||
constructor(
|
||||
|
|
@ -50,6 +51,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||
private renderGeneralSection(): void {
|
||||
this.renderFirstDayOfWeek();
|
||||
this.renderHideCalendar();
|
||||
this.renderEnableCalendarColors();
|
||||
this.renderRefreshInterval();
|
||||
}
|
||||
|
||||
|
|
@ -72,26 +74,57 @@ export class SettingsTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
private async addNewCalendar(): Promise<void> {
|
||||
this.plugin.settings.calendarUrls.push({
|
||||
const newCalendar: CalendarSource = {
|
||||
url: "",
|
||||
name: "New calendar",
|
||||
enabled: true,
|
||||
tags: [],
|
||||
});
|
||||
};
|
||||
|
||||
// Auto-assign color if colors are enabled
|
||||
if (this.plugin.settings.enableCalendarColors) {
|
||||
newCalendar.color = this.getNextAvailableColor();
|
||||
}
|
||||
|
||||
this.plugin.settings.calendarUrls.push(newCalendar);
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
}
|
||||
|
||||
private getNextAvailableColor(): string {
|
||||
const usedColors = this.plugin.settings.calendarUrls
|
||||
.map(source => source.color)
|
||||
.filter(color => color);
|
||||
|
||||
// Find the first unused color in the palette
|
||||
for (const color of CALENDAR_COLOR_PALETTE) {
|
||||
if (!usedColors.includes(color)) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
// If all colors are used, cycle back to the beginning
|
||||
const index = this.plugin.settings.calendarUrls.length % CALENDAR_COLOR_PALETTE.length;
|
||||
return CALENDAR_COLOR_PALETTE[index];
|
||||
}
|
||||
|
||||
private renderCalendarSource(
|
||||
container: HTMLElement,
|
||||
source: CalendarSource,
|
||||
index: number
|
||||
): void {
|
||||
new Setting(container)
|
||||
const setting = new Setting(container)
|
||||
.addText((text) => this.setupUrlInput(text, source, index))
|
||||
.addButton((btn) => this.setupFilePickerButton(btn, index))
|
||||
.addText((text) => this.setupNameInput(text, source, index))
|
||||
.addText((text) => this.setupTagsInput(text, source, index))
|
||||
.addText((text) => this.setupTagsInput(text, source, index));
|
||||
|
||||
// Add color picker if colors are enabled
|
||||
if (this.plugin.settings.enableCalendarColors) {
|
||||
setting.addButton((btn) => this.setupColorPicker(btn, source, index));
|
||||
}
|
||||
|
||||
setting
|
||||
.addToggle((toggle) => this.setupEnabledToggle(toggle, source, index))
|
||||
.addButton((btn) => this.setupRemoveButton(btn, index));
|
||||
}
|
||||
|
|
@ -184,6 +217,42 @@ export class SettingsTab extends PluginSettingTab {
|
|||
});
|
||||
}
|
||||
|
||||
private setupColorPicker(
|
||||
btn: ButtonComponent,
|
||||
source: CalendarSource,
|
||||
index: number
|
||||
): ButtonComponent {
|
||||
const currentColor = source.color || CALENDAR_COLOR_PALETTE[index % CALENDAR_COLOR_PALETTE.length];
|
||||
|
||||
return btn
|
||||
.setButtonText("Color")
|
||||
.setTooltip("Choose calendar color")
|
||||
.onClick(() => {
|
||||
this.showColorPicker(source, index, btn);
|
||||
})
|
||||
.then((button) => {
|
||||
// Add a visual color indicator
|
||||
this.updateColorButton(button.buttonEl, currentColor);
|
||||
return button;
|
||||
});
|
||||
}
|
||||
|
||||
private updateColorButton(buttonEl: HTMLElement, color: string) {
|
||||
buttonEl.style.setProperty("--selected-color", color);
|
||||
buttonEl.classList.add("memochron-color-button");
|
||||
}
|
||||
|
||||
private showColorPicker(source: CalendarSource, index: number, button: ButtonComponent) {
|
||||
const modal = new ColorPickerModal(this.app, source.color || CALENDAR_COLOR_PALETTE[0], async (selectedColor) => {
|
||||
this.plugin.settings.calendarUrls[index].color = selectedColor;
|
||||
await this.plugin.saveSettings();
|
||||
// Force refresh to update event colors
|
||||
await this.plugin.refreshCalendarView(true);
|
||||
this.updateColorButton(button.buttonEl, selectedColor);
|
||||
});
|
||||
modal.open();
|
||||
}
|
||||
|
||||
private renderFirstDayOfWeek(): void {
|
||||
const weekdays = [
|
||||
{ value: "0", label: "Sunday" },
|
||||
|
|
@ -228,6 +297,32 @@ export class SettingsTab extends PluginSettingTab {
|
|||
);
|
||||
}
|
||||
|
||||
private renderEnableCalendarColors(): void {
|
||||
new Setting(this.containerEl)
|
||||
.setName("Enable calendar colors")
|
||||
.setDesc("Show calendars in different colors for easy identification")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.enableCalendarColors)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enableCalendarColors = value;
|
||||
|
||||
// Auto-assign colors to existing calendars if enabling
|
||||
if (value) {
|
||||
this.plugin.settings.calendarUrls.forEach((source, index) => {
|
||||
if (!source.color) {
|
||||
source.color = CALENDAR_COLOR_PALETTE[index % CALENDAR_COLOR_PALETTE.length];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
await this.plugin.refreshCalendarView(true); // Force refresh to update colors
|
||||
this.display(); // Refresh settings display to show/hide color pickers
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private renderRefreshInterval(): void {
|
||||
new Setting(this.containerEl)
|
||||
.setName("Refresh interval")
|
||||
|
|
@ -578,4 +673,55 @@ class FilePickerModal extends SuggestModal<TFile> {
|
|||
onChooseSuggestion(file: TFile) {
|
||||
this.onChoose(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Color picker modal for calendar colors
|
||||
class ColorPickerModal extends Modal {
|
||||
constructor(
|
||||
app: App,
|
||||
private currentColor: string,
|
||||
private onChoose: (color: string) => void
|
||||
) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
contentEl.createEl("h3", { text: "Choose Calendar Color" });
|
||||
|
||||
const colorGrid = contentEl.createEl("div", { cls: "memochron-color-grid" });
|
||||
|
||||
const colorNames = [
|
||||
"Red", "Blue", "Green", "Purple",
|
||||
"Orange", "Yellow", "Pink", "Cyan"
|
||||
];
|
||||
|
||||
CALENDAR_COLOR_PALETTE.forEach((color, index) => {
|
||||
const colorButton = colorGrid.createEl("button", {
|
||||
cls: "memochron-color-option",
|
||||
attr: { title: colorNames[index] }
|
||||
});
|
||||
|
||||
colorButton.style.setProperty("--color", color);
|
||||
|
||||
if (color === this.currentColor) {
|
||||
colorButton.classList.add("selected");
|
||||
}
|
||||
|
||||
colorButton.addEventListener("click", () => {
|
||||
this.onChoose(color);
|
||||
this.close();
|
||||
});
|
||||
|
||||
// Add color name label
|
||||
colorButton.createEl("span", { text: colorNames[index] });
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ export interface CalendarSource {
|
|||
name: string;
|
||||
enabled: boolean;
|
||||
tags: string[];
|
||||
color?: string; // Optional color for this calendar
|
||||
}
|
||||
|
||||
export interface MemoChronSettings {
|
||||
|
|
@ -30,6 +31,7 @@ export interface MemoChronSettings {
|
|||
firstDayOfWeek: number; // 0 = Sunday, 1 = Monday, etc.
|
||||
hideCalendar: boolean;
|
||||
folderPathTemplate: string; // Template for organizing notes in date-based subfolders
|
||||
enableCalendarColors: boolean; // Global toggle for calendar colors feature
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: MemoChronSettings = {
|
||||
|
|
@ -56,4 +58,5 @@ export const DEFAULT_SETTINGS: MemoChronSettings = {
|
|||
firstDayOfWeek: DEFAULT_FIRST_DAY_OF_WEEK,
|
||||
hideCalendar: false,
|
||||
folderPathTemplate: "", // Empty by default for backwards compatibility
|
||||
enableCalendarColors: false, // Disabled by default
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,4 +20,17 @@ export const TEMPLATE_VARIABLES = {
|
|||
DATE: '{{date}}',
|
||||
DESCRIPTION: '{{description}}',
|
||||
LOCATION: '{{location}}',
|
||||
};
|
||||
};
|
||||
|
||||
// Color palette for auto-assigning calendar colors
|
||||
// Using CSS variables that work well with Obsidian themes
|
||||
export const CALENDAR_COLOR_PALETTE = [
|
||||
'var(--color-red)',
|
||||
'var(--color-blue)',
|
||||
'var(--color-green)',
|
||||
'var(--color-purple)',
|
||||
'var(--color-orange)',
|
||||
'var(--color-yellow)',
|
||||
'var(--color-pink)',
|
||||
'var(--color-cyan)',
|
||||
];
|
||||
|
|
@ -57,6 +57,9 @@ export class CalendarView extends ItemView {
|
|||
);
|
||||
this.renderMonth();
|
||||
|
||||
// Update calendar visibility based on current settings
|
||||
this.updateCalendarVisibility();
|
||||
|
||||
// Always show agenda for selected date or today
|
||||
const dateToShow = this.selectedDate || new Date();
|
||||
this.showDayAgenda(dateToShow);
|
||||
|
|
@ -285,10 +288,38 @@ export class CalendarView extends ItemView {
|
|||
|
||||
if (events.length > 0) {
|
||||
dayEl.addClass("has-events");
|
||||
dayEl.createEl("div", {
|
||||
cls: "memochron-event-dot",
|
||||
text: "•",
|
||||
});
|
||||
|
||||
if (this.plugin.settings.enableCalendarColors) {
|
||||
// Group events by calendar source to show one dot per calendar
|
||||
const eventsBySource = new Map<string, CalendarEvent>();
|
||||
events.forEach(event => {
|
||||
if (!eventsBySource.has(event.sourceId)) {
|
||||
eventsBySource.set(event.sourceId, event);
|
||||
}
|
||||
});
|
||||
|
||||
// Create a container for dots
|
||||
const dotsContainer = dayEl.createEl("div", {
|
||||
cls: "memochron-event-dots-container"
|
||||
});
|
||||
|
||||
// Add a colored dot for each calendar that has events
|
||||
eventsBySource.forEach(event => {
|
||||
const dot = dotsContainer.createEl("div", {
|
||||
cls: "memochron-event-dot colored",
|
||||
text: "•",
|
||||
});
|
||||
if (event.color) {
|
||||
dot.style.color = event.color;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Single dot for all events when colors are disabled
|
||||
dayEl.createEl("div", {
|
||||
cls: "memochron-event-dot",
|
||||
text: "•",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,6 +373,12 @@ export class CalendarView extends ItemView {
|
|||
eventEl.addClass("past-event");
|
||||
}
|
||||
|
||||
// Add colored left border if colors are enabled
|
||||
if (this.plugin.settings.enableCalendarColors && event.color) {
|
||||
eventEl.addClass("with-color");
|
||||
eventEl.style.setProperty("--event-color", event.color);
|
||||
}
|
||||
|
||||
this.renderEventTime(eventEl, event);
|
||||
this.renderEventTitle(eventEl, event);
|
||||
this.renderEventLocation(eventEl, event);
|
||||
|
|
|
|||
174
styles.css
174
styles.css
|
|
@ -153,6 +153,21 @@
|
|||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* Colored dots container */
|
||||
.memochron-event-dots-container {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.memochron-event-dot.colored {
|
||||
font-size: 1em;
|
||||
height: auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Week View */
|
||||
.memochron-week-grid {
|
||||
display: grid;
|
||||
|
|
@ -274,18 +289,108 @@
|
|||
border-radius: var(--radius-s);
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.memochron-agenda-event:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
/* Colored left border for events */
|
||||
.memochron-agenda-event.with-color {
|
||||
padding-left: calc(var(--memochron-padding-sm) + 4px);
|
||||
}
|
||||
|
||||
.memochron-agenda-event.with-color::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
background-color: var(--event-color);
|
||||
border-radius: var(--radius-s) 0 0 var(--radius-s);
|
||||
}
|
||||
|
||||
/* Grey out past events in agenda view */
|
||||
.memochron-agenda-event.past-event {
|
||||
opacity: 0.8;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Color picker styles */
|
||||
.memochron-color-button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.memochron-color-button::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--selected-color);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.memochron-color-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--size-4-2);
|
||||
margin: var(--size-4-4) 0;
|
||||
}
|
||||
|
||||
.memochron-color-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--size-4-3);
|
||||
border: 2px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
background: var(--background-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.memochron-color-option::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: var(--size-4-2);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.memochron-color-option span {
|
||||
margin-left: var(--size-4-6);
|
||||
font-size: 0.9em;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.memochron-color-option:hover {
|
||||
border-color: var(--interactive-accent);
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.memochron-color-option.selected {
|
||||
border-color: var(--interactive-accent);
|
||||
background: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.memochron-color-option.selected span {
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.memochron-event-time {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85em;
|
||||
|
|
@ -323,6 +428,75 @@
|
|||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Improve calendar source settings layout */
|
||||
.memochron-calendar-list .setting-item {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.memochron-calendar-list .setting-item .setting-item-control {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--size-4-2);
|
||||
align-items: flex-start;
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
|
||||
.memochron-calendar-list .setting-item .setting-item-control > * {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* URL input should be wider */
|
||||
.memochron-calendar-list .setting-item input[type="text"]:first-of-type {
|
||||
min-width: 200px;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
/* Name input */
|
||||
.memochron-calendar-list .setting-item input[type="text"]:nth-of-type(2) {
|
||||
min-width: 100px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Tags input */
|
||||
.memochron-calendar-list .setting-item input[type="text"]:nth-of-type(3) {
|
||||
min-width: 80px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Color button styling */
|
||||
.memochron-color-button {
|
||||
min-width: 70px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* File picker button */
|
||||
.memochron-calendar-list .setting-item button[aria-label*="folder"] {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Toggle and remove buttons */
|
||||
.memochron-calendar-list .setting-item .checkbox-container,
|
||||
.memochron-calendar-list .setting-item button:last-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Force break to new line when needed */
|
||||
@media screen and (max-width: 1200px) {
|
||||
.memochron-calendar-list .setting-item .setting-item-control {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.memochron-calendar-list .setting-item .setting-item-control > * {
|
||||
width: 100%;
|
||||
margin-bottom: var(--size-4-1) !important;
|
||||
}
|
||||
|
||||
.memochron-calendar-list .setting-item input[type="text"] {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.memochron-setting-item-container {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue