feat: add coloured calendar system indicators

Add a selectable single-dot or per-system indicator mode. Date systems now carry configurable colours; results are cached per displayed month and invalidated when paths/settings change. Refine calendar hierarchy, spacing, hover, and today states.
This commit is contained in:
2026-09-07 21:43:21 -04:00
parent 12209155dc
commit 069456799e
9 changed files with 305 additions and 72 deletions
+27 -17
View File
@@ -51,7 +51,7 @@ Detaches all leaves of `WAYPOINT_VIEW_TYPE`.
```typescript ```typescript
interface WaypointSettings { interface WaypointSettings {
calendar: CalendarSettings; // firstDayOfWeek (0=Sun,1=Mon), showNoteIndicators calendar: CalendarSettings; // week start, indicator visibility/style, daily indicator colour
daily: PeriodNoteSettings; daily: PeriodNoteSettings;
weekly: PeriodNoteSettings; weekly: PeriodNoteSettings;
monthly: PeriodNoteSettings; monthly: PeriodNoteSettings;
@@ -71,7 +71,9 @@ interface PeriodNoteSettings {
interface CalendarSettings { interface CalendarSettings {
firstDayOfWeek: number; // 0 = Sunday, 1 = Monday firstDayOfWeek: number; // 0 = Sunday, 1 = Monday
showNoteIndicators: boolean; // dot on days with existing .md files showNoteIndicators: boolean; // show or hide all calendar markers
indicatorMode: 'any' | 'systems'; // one neutral dot, or one coloured dot per date system
dailyIndicatorColor: string; // daily's colour in systems mode
} }
interface RecentFilesSettings { interface RecentFilesSettings {
@@ -157,7 +159,6 @@ A **date system** is a folder of notes whose filenames begin with a date. Daily
### `DateSystemSettings` ### `DateSystemSettings`
```typescript
interface DateSystemSettings { interface DateSystemSettings {
id: string; // stable across edits + reordering, so settings rows can key on it id: string; // stable across edits + reordering, so settings rows can key on it
name: string; // menu label, e.g. "Journal" name: string; // menu label, e.g. "Journal"
@@ -166,15 +167,16 @@ interface DateSystemSettings {
templateFile: string; // may omit the .md extension templateFile: string; // may omit the .md extension
typeProperty: string; // fallback frontmatter `type:` value typeProperty: string; // fallback frontmatter `type:` value
icon: string; // Lucide icon name for the menu item icon: string; // Lucide icon name for the menu item
indicatorColor: string; // calendar dot colour in systems mode
} }
``` ```
Configured systems live in `settings.dateSystems`, where array order is menu order. `DEFAULT_DATE_SYSTEM` (in `settings.ts`, typed `Omit<DateSystemSettings, 'id'>`) supplies the field defaults for a newly added row **and** the merge base for saved ones, so a system persisted before a field existed still loads with that field defined. `DEFAULT_SETTINGS.dateSystems` ships two: Configured systems live in `settings.dateSystems`, where array order is menu order. `DEFAULT_DATE_SYSTEM` (in `settings.ts`, typed `Omit<DateSystemSettings, 'id'>`) supplies the field defaults for a newly added row **and** the merge base for saved ones, so a system persisted before a field existed still loads with that field defined. `DEFAULT_SETTINGS.dateSystems` ships two:
| `id` | `name` | Folder | `nameFormat` | Notes per date | | `id` | `name` | Folder | `nameFormat` | Indicator colour | Notes per date |
|---|---|---|---|---| |---|---|---|---|---|---|
| `journal` | Journal | `periodic/journal` | `YYYY-MM-DD - [Journal]` | one | | `journal` | Journal | `periodic/journal` | `YYYY-MM-DD - [Journal]` | green (`#22c55e`) | one |
| `meetings` | Meeting | `periodic/meetings` | `YYYY-MM-DD - {title}` | many | | `meetings` | Meeting | `periodic/meetings` | `YYYY-MM-DD - {title}` | violet (`#a855f7`) | many |
### Templater folder triggers ### Templater folder triggers
@@ -210,21 +212,26 @@ Imports nothing from `'obsidian'` — callers make every moment call and pass th
### Plugin API (`main.ts`) ### Plugin API (`main.ts`)
```typescript ```typescript
/** One existing note of a date system, with the label the menu should show. */
interface DateSystemNote { interface DateSystemNote {
file: TFile; file: TFile;
label: string; // free-text title for many-per-date systems, else the system name label: string; // free-text title for many-per-date systems, else the system name
} }
/** A day-scoped system and the notes it already holds for one date. */
interface DateSystemNotes { interface DateSystemNotes {
system: DateSystemSettings; system: DateSystemSettings;
notes: DateSystemNote[]; // sorted by basename; empty when the date has no note notes: DateSystemNote[];
multiple: boolean; // nameFormat carries {title}, i.e. many notes per date multiple: boolean; // nameFormat carries {title}
}
interface DateSystemIndicator {
id: string;
name: string;
color: string;
} }
dateSystems(): DateSystemSettings[] dateSystems(): DateSystemSettings[]
findDateSystemNotes(date: moment.Moment): DateSystemNotes[] findDateSystemNotes(date: moment.Moment): DateSystemNotes[]
getDateSystemIndicators(dates: moment.Moment[]): Map<string, DateSystemIndicator[]>
openDateSystemNote( openDateSystemNote(
system: DateSystemSettings, system: DateSystemSettings,
date: moment.Moment, date: moment.Moment,
@@ -232,6 +239,8 @@ openDateSystemNote(
): Promise<void> ): Promise<void>
``` ```
**`getDateSystemIndicators(dates)`** — returns one `{ id, name, color }` marker per configured system with one or more notes on a requested day. The calendar calls it once for the visible month only in `indicatorMode: 'systems'`. It makes one vault pass, caches the result by displayed dates and date-system settings, and invalidates it on markdown create/delete/rename or any Settings change. It does not run on ordinary markdown edits: content edits cannot change a filename-derived indicator.
**`dateSystems()`** — every day-scoped system in menu order: the daily periodic note, synthesized into a `DateSystemSettings` from `settings.daily`, followed by `settings.dateSystems`. The daily note is therefore not a special case in any consumer. **`dateSystems()`** — every day-scoped system in menu order: the daily periodic note, synthesized into a `DateSystemSettings` from `settings.daily`, followed by `settings.dateSystems`. The daily note is therefore not a special case in any consumer.
**`findDateSystemNotes(date)`** — **one** vault scan per call, not one per system. It formats each usable system's `before`/`after` affixes for `date` once, then walks the markdown file list a single time, bucketing each file into the system that claims it (`isInFolder` + `matchesSystemName`). Systems whose formats have no date token are omitted rather than offering an unsafe create action. It returns the remaining `DateSystemNotes` in `dateSystems()` order, each bucket sorted by basename. The calendar's day context menu is built from exactly one of these calls per right-click. **`findDateSystemNotes(date)`** — **one** vault scan per call, not one per system. It formats each usable system's `before`/`after` affixes for `date` once, then walks the markdown file list a single time, bucketing each file into the system that claims it (`isInFolder` + `matchesSystemName`). Systems whose formats have no date token are omitted rather than offering an unsafe create action. It returns the remaining `DateSystemNotes` in `dateSystems()` order, each bucket sorted by basename. The calendar's day context menu is built from exactly one of these calls per right-click.
@@ -348,7 +357,7 @@ The last section (`waypoint-section:last-child`) gets `margin-top: auto`, pushin
- **Breadcrumb:** Q-label, month name, year — each clickable to open that period note. - **Breadcrumb:** Q-label, month name, year — each clickable to open that period note.
- **Nav buttons:** ◀/▶ shift month ±1. "Today" resets to current month. - **Nav buttons:** ◀/▶ shift month ±1. "Today" resets to current month.
- **Week number column:** Clicking a week number opens the weekly note for that week's Monday; middle-clicking opens it in a new tab. - **Week number column:** Clicking a week number opens the weekly note for that week's Monday; middle-clicking opens it in a new tab.
- **Day cells:** Left-click opens the daily note, middle-click opens it in a new tab, right-click opens the day context menu (below). `.other-month` dimmed. `.today` has accent border. `.has-note` gets a dot indicator, decided by the synchronous O(1) `plugin.hasNoteForDate(dateStr)` (a `Set` lookup — no per-cell vault scan). - **Day cells:** Left-click opens the daily note, middle-click opens it in a new tab, right-click opens the day context menu (below). `.other-month` is muted; `.today` has an inset accent ring; hover receives a quiet fill. With `indicatorMode: 'any'`, `.has-note` shows the existing single neutral dot through the O(1) `plugin.hasNoteForDate(dateStr)` lookup. With `indicatorMode: 'systems'`, the view calls `getDateSystemIndicators()` once for the month and renders one tooltip-labelled coloured dot per matching daily/journal/meeting/custom system.
- **Grid generation:** `getMonthGrid(year, month, firstDayOfWeek)` in `date-utils.ts` produces up to 6 weeks, each with 7 `CalendarDay` objects containing `moment`, `dayOfMonth`, `isToday`, `isCurrentMonth`, `isoWeekNumber`. - **Grid generation:** `getMonthGrid(year, month, firstDayOfWeek)` in `date-utils.ts` produces up to 6 weeks, each with 7 `CalendarDay` objects containing `moment`, `dayOfMonth`, `isToday`, `isCurrentMonth`, `isoWeekNumber`.
**Day context menu (right-click):** Built from a single `plugin.findDateSystemNotes(day.date)` call. The first entry is a non-clickable header showing the full date. Then, per system in `dateSystems()` order: **Day context menu (right-click):** Built from a single `plugin.findDateSystemNotes(day.date)` call. The first entry is a non-clickable header showing the full date. Then, per system in `dateSystems()` order:
@@ -423,17 +432,18 @@ All classes prefixed with `waypoint-`. Uses Obsidian CSS variables throughout:
- `--font-ui-small`, `--font-ui-medium`, `--font-semibold`, `--font-medium`, `--font-light` - `--font-ui-small`, `--font-ui-medium`, `--font-semibold`, `--font-medium`, `--font-light`
- `--text-muted`, `--text-faint`, `--text-accent`, `--text-on-accent`, `--text-error` - `--text-muted`, `--text-faint`, `--text-accent`, `--text-on-accent`, `--text-error`
- `--background-modifier-border`, `--background-modifier-active-hover`, `--background-primary`, `--background-secondary` - `--background-modifier-border`, `--background-modifier-active-hover`, `--background-modifier-hover`, `--background-primary`, `--background-primary-alt`, `--background-secondary`
- `--interactive-accent` - `--interactive-accent`
- `--cursor-link` (pointer cursor, with a `pointer` fallback) - `--cursor-link` (pointer cursor, with a `pointer` fallback)
Key layout: Key layout:
- `.waypoint-view` — flex column, `overflow-y: auto`, 8px padding. - `.waypoint-view` — flex column, `overflow-y: auto`, 8px padding.
- `.waypoint-section``flex-shrink: 0`, 16px bottom margin. Last section gets `margin-top: auto` (pins calendar to bottom). - `.waypoint-section``flex-shrink: 0`, 16px bottom margin. Last section gets `margin-top: auto` (pins calendar to bottom).
- `.waypoint-section-header` — uppercase, muted, with bottom border. - `.waypoint-calendar` — padded, bordered surface with separated day cells for clear scan lines.
- Calendar table — `table-layout: fixed`, `border-collapse: collapse`. - Calendar table — `table-layout: fixed`, `border-collapse: separate`, 2px horizontal / 3px vertical cell spacing.
- `.waypoint-day.today` — accent color text + 1px accent border. - `.waypoint-day.today` — accent text with an inset accent ring, avoiding layout shifts.
- `.waypoint-day.has-note::after`4px dot indicator. - `.waypoint-day.has-note::after`neutral single-dot indicator.
- `.waypoint-day-indicators` / `.waypoint-day-indicator` — compact ordered dot row; `--waypoint-indicator-color` carries each configured system colour.
- `.waypoint-bm-chevron``rotate(-90deg)` on collapsed groups. - `.waypoint-bm-chevron``rotate(-90deg)` on collapsed groups.
- `.waypoint-bm-drop-line` / `.waypoint-bm-drop-below` — 3px accent border for drag indicators. - `.waypoint-bm-drop-line` / `.waypoint-bm-drop-below` — 3px accent border for drag indicators.
+11
View File
@@ -70,6 +70,17 @@
- [ ] Note indicator dots show on days with existing .md files - [ ] Note indicator dots show on days with existing .md files
- [ ] Today is highlighted with accent border - [ ] Today is highlighted with accent border
## Calendar: System-coloured indicators
- [ ] Calendar → Indicator style → **Single dot** keeps one neutral dot for an existing date-named note
- [ ] Calendar → Indicator style → **Colour by note system** shows a blue dot for Daily, green for Journal, and violet for Meeting by default
- [ ] A day holding multiple meeting notes still shows exactly one Meeting dot
- [ ] A day holding Daily, Journal, and Meeting notes shows three compact dots in that order
- [ ] Hover each coloured dot → its date-system name is identified by a tooltip
- [ ] Change Daily/Journal/Meeting indicator colours in Settings → the visible calendar refreshes immediately
- [ ] Add a custom date system with its own colour and a matching note → its dot appears in system order
- [ ] Create, delete, or rename a date-system note → coloured dots refresh without changing months
## Calendar: Date systems ## Calendar: Date systems
+11 -2
View File
@@ -4,8 +4,8 @@ Calendar, recent files, and custom bookmarks sidebar for Obsidian.
## Features ## Features
- **Calendar panel** — month grid; left-click a day opens its daily note, middle-click opens it in a tab, and right-click reveals every configured note system for that date - **Calendar panel** — refined month grid; left-click a day opens its daily note, middle-click opens it in a tab, and right-click reveals every configured note system for that date
- **Date systems** — browse or create daily, journal, meeting, and other date-prefixed notes from a single day menu - **Calendar indicators** — retain a single neutral note dot or show a coloured dot per daily, journal, meeting, or custom date system
- **Recent files** — track recently opened/edited files - **Recent files** — track recently opened/edited files
- **Favorites** — custom bookmarks with groups, icons, and rename - **Favorites** — custom bookmarks with groups, icons, and rename
@@ -35,6 +35,15 @@ Configure systems in **Settings → Waypoint Sidebar → Date systems**. Each sy
Ctrl/Cmd-click a menu entry opens it in a new tab. Ctrl/Cmd-click a menu entry opens it in a new tab.
### Calendar indicators
Under **Settings → Waypoint Sidebar → Calendar**, choose either:
- **Single dot** — the existing neutral marker for any note whose filename is exactly the date.
- **Colour by note system** — one compact coloured dot per configured date system that has a note on that day. Hover a dot to identify its system.
Daily uses the Calendar tabs colour picker. Journal, Meeting, and every custom system have their own **Indicator colour** picker under **Date systems**. The defaults are blue for Daily, green for Journal, and violet for Meeting.
### Templater folder templates ### Templater folder templates
If a date system uses a Templater folder mapping, leave its Waypoint **Template file** field blank. Waypoint then creates a safe fallback note while Templater owns prompts, scripts, cursors, and rendered content. If a date system uses a Templater folder mapping, leave its Waypoint **Template file** field blank. Waypoint then creates a safe fallback note while Templater owns prompts, scripts, cursors, and rendered content.
+18 -18
View File
File diff suppressed because one or more lines are too long
+97 -3
View File
@@ -42,6 +42,13 @@ export interface DateSystemNotes {
multiple: boolean; multiple: boolean;
} }
/** One coloured calendar marker for a date system that has a note on a day. */
export interface DateSystemIndicator {
id: string;
name: string;
color: string;
}
export default class WaypointPlugin extends Plugin { export default class WaypointPlugin extends Plugin {
public settings: WaypointSettings; public settings: WaypointSettings;
public waypointData: WaypointData; public waypointData: WaypointData;
@@ -51,6 +58,9 @@ export default class WaypointPlugin extends Plugin {
private savePromise: Promise<void> = Promise.resolve(); private savePromise: Promise<void> = Promise.resolve();
/** Basenames of every markdown file in the vault, for O(1) calendar lookups. */ /** Basenames of every markdown file in the vault, for O(1) calendar lookups. */
private markdownBasenames: Set<string> = new Set(); private markdownBasenames: Set<string> = new Set();
/** Results for the displayed month; invalidated by markdown-file changes. */
private dateSystemIndicators: Map<string, DateSystemIndicator[]> | null = null;
private dateSystemIndicatorKey = '';
async onload(): Promise<void> { async onload(): Promise<void> {
console.debug('Waypoint: loading plugin v' + this.manifest.version); console.debug('Waypoint: loading plugin v' + this.manifest.version);
@@ -73,6 +83,7 @@ export default class WaypointPlugin extends Plugin {
this.settings, this.settings,
() => { () => {
this.enforceRecentFilesLimit(); this.enforceRecentFilesLimit();
this.invalidateDateSystemIndicators();
this.redrawAll(); this.redrawAll();
}, },
)); ));
@@ -247,10 +258,14 @@ export default class WaypointPlugin extends Plugin {
} }
// Cloned, not Object.assign'd in: the defaults array would otherwise be // Cloned, not Object.assign'd in: the defaults array would otherwise be
// aliased into the live settings and the settings UI would edit // aliased into the live settings and the settings UI would edit
// DEFAULT_SETTINGS itself. Saved entries merge over // DEFAULT_DATE_SYSTEM so older configs pick up fields added since. Known
// DEFAULT_DATE_SYSTEM so older configs pick up fields added since. // built-ins use their own defaults too, preserving Journal/Meeting colours
// when this field is first introduced.
this.settings.dateSystems = Array.isArray(s.dateSystems) this.settings.dateSystems = Array.isArray(s.dateSystems)
? s.dateSystems.map(sys => Object.assign({}, DEFAULT_DATE_SYSTEM, sys)) ? s.dateSystems.map(sys => {
const builtIn = DEFAULT_SETTINGS.dateSystems.find(defaultSystem => defaultSystem.id === sys.id);
return Object.assign({}, builtIn || DEFAULT_DATE_SYSTEM, sys);
})
: DEFAULT_SETTINGS.dateSystems.map(sys => Object.assign({}, sys)); : DEFAULT_SETTINGS.dateSystems.map(sys => Object.assign({}, sys));
} }
@@ -392,6 +407,7 @@ export default class WaypointPlugin extends Plugin {
* inside a renamed folder. * inside a renamed folder.
*/ */
private onRename(file: TAbstractFile, oldPath: string): void { private onRename(file: TAbstractFile, oldPath: string): void {
this.invalidateDateSystemIndicators();
const indexChanged = this.syncIndexForRename(file, oldPath); const indexChanged = this.syncIndexForRename(file, oldPath);
let dataChanged = false; let dataChanged = false;
@@ -427,6 +443,7 @@ export default class WaypointPlugin extends Plugin {
private onVaultCreate(file: TAbstractFile): void { private onVaultCreate(file: TAbstractFile): void {
if (file instanceof TFile && file.extension === 'md') { if (file instanceof TFile && file.extension === 'md') {
this.markdownBasenames.add(file.basename); this.markdownBasenames.add(file.basename);
this.invalidateDateSystemIndicators();
} }
this.broadcastRedraw(); this.broadcastRedraw();
} }
@@ -434,6 +451,7 @@ export default class WaypointPlugin extends Plugin {
private onVaultDelete(file: TAbstractFile): void { private onVaultDelete(file: TAbstractFile): void {
if (file instanceof TFile && file.extension === 'md') { if (file instanceof TFile && file.extension === 'md') {
this.removeFromMarkdownIndex(file.basename, file.path); this.removeFromMarkdownIndex(file.basename, file.path);
this.invalidateDateSystemIndicators();
} }
this.broadcastRedraw(); this.broadcastRedraw();
} }
@@ -447,6 +465,12 @@ export default class WaypointPlugin extends Plugin {
} }
} }
/** Forget a month result when markdown paths or date-system settings change. */
private invalidateDateSystemIndicators(): void {
this.dateSystemIndicators = null;
this.dateSystemIndicatorKey = '';
}
/** /**
* Drop a basename from the index, unless another markdown file still * Drop a basename from the index, unless another markdown file still
* carries it. `path` is excluded from that check because the vault may not * carries it. `path` is excluded from that check because the vault may not
@@ -555,6 +579,7 @@ export default class WaypointPlugin extends Plugin {
templateFile: periodSettings.templateFile, templateFile: periodSettings.templateFile,
typeProperty: periodSettings.typeProperty, typeProperty: periodSettings.typeProperty,
icon: 'calendar', icon: 'calendar',
indicatorColor: this.settings.calendar.dailyIndicatorColor,
}; };
} }
@@ -609,6 +634,75 @@ export default class WaypointPlugin extends Plugin {
.map(bucket => bucket.result); .map(bucket => bucket.result);
} }
/**
* Return one coloured indicator per system with a note on each requested day.
*
* The day grid calls this once per render. Its result is cached by the
* displayed dates and system settings, then invalidated by file changes, so
* a 42-cell calendar never repeats the vault scan per cell or per redraw.
*/
getDateSystemIndicators(dates: moment.Moment[]): Map<string, DateSystemIndicator[]> {
const dateStrings = dates.map(date => date.format('YYYY-MM-DD'));
const systems = this.dateSystems()
.filter(system => formatHasDateToken(system.nameFormat))
.map(system => {
const parts = splitNameFormat(system.nameFormat);
return {
system,
hasTitle: parts.hasTitle,
dates: dates.map((date, index) => ({
dateStr: dateStrings[index],
before: formatDatePart(date, parts.before),
after: formatDatePart(date, parts.after),
})),
};
});
const key = JSON.stringify({
dates: dateStrings,
systems: systems.map(({ system }) => [
system.id,
system.name,
system.folder,
system.nameFormat,
system.indicatorColor,
]),
});
if (this.dateSystemIndicators && this.dateSystemIndicatorKey === key) {
return this.dateSystemIndicators;
}
const matchingIds = new Map<string, Set<string>>();
for (const dateStr of dateStrings) matchingIds.set(dateStr, new Set());
// The only vault traversal: filter a file by system folder first, then
// test it against the month's at-most-42 formatted day patterns.
for (const file of this.app.vault.getMarkdownFiles()) {
for (const entry of systems) {
if (!isInFolder(file.path, entry.system.folder)) continue;
for (const match of entry.dates) {
if (matchesSystemName(file.basename, match.before, match.after, entry.hasTitle)) {
matchingIds.get(match.dateStr)?.add(entry.system.id);
}
}
}
}
const result = new Map<string, DateSystemIndicator[]>();
for (const dateStr of dateStrings) {
const ids = matchingIds.get(dateStr);
result.set(dateStr, systems
.filter(({ system }) => ids?.has(system.id))
.map(({ system }) => ({
id: system.id,
name: system.name,
color: system.indicatorColor,
})));
}
this.dateSystemIndicatorKey = key;
this.dateSystemIndicators = result;
return result;
}
/** /**
* Open (creating if needed) the note `system` holds for `date`. * Open (creating if needed) the note `system` holds for `date`.
* *
+38
View File
@@ -96,6 +96,32 @@ export class WaypointSettingTab extends PluginSettingTab {
this.saveAndRefresh(); this.saveAndRefresh();
}); });
}); });
new Setting(container)
.setName('Indicator style')
.setDesc('Show one neutral dot for any dated note, or one coloured dot for each configured date system.')
.addDropdown((dropdown) => {
dropdown
.addOption('any', 'Single dot')
.addOption('systems', 'Colour by note system')
.setValue(this.settings.calendar.indicatorMode)
.onChange((value: 'any' | 'systems') => {
this.settings.calendar.indicatorMode = value;
this.saveAndRefresh();
});
});
new Setting(container)
.setName('Daily indicator colour')
.setDesc('Colour for daily-note dots when using “Colour by note system”.')
.addColorPicker((color) => {
color
.setValue(this.settings.calendar.dailyIndicatorColor)
.onChange((value) => {
this.settings.calendar.dailyIndicatorColor = value;
this.saveAndRefresh();
});
});
} }
// ═══════════════════════════════ // ═══════════════════════════════
@@ -254,6 +280,18 @@ export class WaypointSettingTab extends PluginSettingTab {
'Icon', 'Lucide icon name for the menu item. Browse names at lucide.dev.', 'Icon', 'Lucide icon name for the menu item. Browse names at lucide.dev.',
system, 'icon', 'book-open', system, 'icon', 'book-open',
); );
new Setting(container)
.setName('Indicator colour')
.setDesc('Colour for this systems dot when using “Colour by note system”.')
.addColorPicker((color) => {
color
.setValue(system.indicatorColor)
.onChange((value) => {
system.indicatorColor = value;
this.saveAndRefresh();
});
});
}); });
new Setting(container) new Setting(container)
+11
View File
@@ -27,6 +27,8 @@ export interface DateSystemSettings {
typeProperty: string; typeProperty: string;
/** Lucide icon name for the menu item. */ /** Lucide icon name for the menu item. */
icon: string; icon: string;
/** CSS colour for this system's calendar indicator. */
indicatorColor: string;
} }
/** Field defaults for a newly added system, and the merge base for saved ones. */ /** Field defaults for a newly added system, and the merge base for saved ones. */
@@ -37,11 +39,16 @@ export const DEFAULT_DATE_SYSTEM: Omit<DateSystemSettings, 'id'> = {
templateFile: '', templateFile: '',
typeProperty: '', typeProperty: '',
icon: 'file', icon: 'file',
indicatorColor: '#64748b',
}; };
export interface CalendarSettings { export interface CalendarSettings {
firstDayOfWeek: number; // 0=Sunday, 1=Monday firstDayOfWeek: number; // 0=Sunday, 1=Monday
showNoteIndicators: boolean; showNoteIndicators: boolean;
/** Single neutral dot, or a dot per day-scoped date system. */
indicatorMode: 'any' | 'systems';
/** Colour used by the synthesized daily system in `systems` mode. */
dailyIndicatorColor: string;
} }
export interface RecentFilesSettings { export interface RecentFilesSettings {
@@ -78,6 +85,8 @@ export const DEFAULT_SETTINGS: WaypointSettings = {
calendar: { calendar: {
firstDayOfWeek: 1, // Monday firstDayOfWeek: 1, // Monday
showNoteIndicators: true, showNoteIndicators: true,
indicatorMode: 'any',
dailyIndicatorColor: '#3b82f6',
}, },
daily: { daily: {
folder: 'periodic/daily', folder: 'periodic/daily',
@@ -118,6 +127,7 @@ export const DEFAULT_SETTINGS: WaypointSettings = {
templateFile: '', templateFile: '',
typeProperty: 'journal', typeProperty: 'journal',
icon: 'book-open', icon: 'book-open',
indicatorColor: '#22c55e',
}, },
{ {
id: 'meetings', id: 'meetings',
@@ -127,6 +137,7 @@ export const DEFAULT_SETTINGS: WaypointSettings = {
templateFile: '', templateFile: '',
typeProperty: 'meeting', typeProperty: 'meeting',
icon: 'users', icon: 'users',
indicatorColor: '#a855f7',
}, },
], ],
recentFiles: { recentFiles: {
+20
View File
@@ -181,6 +181,14 @@ export class WaypointView extends ItemView {
this.plugin.settings.calendar.firstDayOfWeek, this.plugin.settings.calendar.firstDayOfWeek,
); );
const systemIndicators = this.plugin.settings.calendar.showNoteIndicators
&& this.plugin.settings.calendar.indicatorMode === 'systems'
? this.plugin.getDateSystemIndicators(weeks.reduce<moment.Moment[]>(
(dates, week) => dates.concat(week.days.map(day => day.date)),
[],
))
: null;
for (const week of weeks) { for (const week of weeks) {
const row = tbody.createEl('tr'); const row = tbody.createEl('tr');
@@ -211,9 +219,21 @@ export class WaypointView extends ItemView {
if (this.plugin.settings.calendar.showNoteIndicators) { if (this.plugin.settings.calendar.showNoteIndicators) {
const dateStr = day.date.format('YYYY-MM-DD'); const dateStr = day.date.format('YYYY-MM-DD');
if (this.plugin.settings.calendar.indicatorMode === 'any') {
if (this.plugin.hasNoteForDate(dateStr)) { if (this.plugin.hasNoteForDate(dateStr)) {
cell.addClass('has-note'); cell.addClass('has-note');
} }
} else {
const indicators = systemIndicators?.get(dateStr) || [];
if (indicators.length > 0) {
const dots = cell.createDiv({ cls: 'waypoint-day-indicators' });
for (const indicator of indicators) {
const dot = dots.createSpan({ cls: 'waypoint-day-indicator' });
dot.style.setProperty('--waypoint-indicator-color', indicator.color);
setTooltip(dot, indicator.name);
}
}
}
} }
cell.addEventListener('click', () => { cell.addEventListener('click', () => {
+60 -20
View File
@@ -58,29 +58,34 @@
.waypoint-calendar { .waypoint-calendar {
font-size: var(--font-ui-small); font-size: var(--font-ui-small);
padding: 8px;
border: 1px solid var(--background-modifier-border);
border-radius: 10px;
background: var(--background-primary-alt);
} }
.waypoint-calendar-top { .waypoint-calendar-top {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 10px; margin-bottom: 8px;
} }
.waypoint-calendar-breadcrumb { .waypoint-calendar-breadcrumb {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 4px;
font-size: var(--font-ui-medium); font-size: var(--font-ui-medium);
font-weight: var(--font-semibold); font-weight: var(--font-semibold);
padding: 4px 0; padding: 2px 0;
} }
.waypoint-calendar-breadcrumb .waypoint-clickable { .waypoint-calendar-breadcrumb .waypoint-clickable {
color: var(--text-muted); color: var(--text-muted);
cursor: var(--cursor-link, pointer); cursor: var(--cursor-link, pointer);
padding: 1px 4px; padding: 2px 4px;
border-radius: 4px; border-radius: 4px;
transition: color 80ms, background-color 80ms;
} }
.waypoint-calendar-breadcrumb .waypoint-clickable:hover { .waypoint-calendar-breadcrumb .waypoint-clickable:hover {
@@ -90,23 +95,25 @@
.waypoint-calendar .waypoint-separator { .waypoint-calendar .waypoint-separator {
color: var(--text-faint); color: var(--text-faint);
margin: 0 2px; margin: 0;
} }
.waypoint-calendar-today-group { .waypoint-calendar-today-group {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 2px; gap: 1px;
} }
.waypoint-calendar-today-group button { .waypoint-calendar-today-group button {
background: none; background: none;
border: none; border: none;
cursor: var(--cursor-link, pointer); cursor: var(--cursor-link, pointer);
padding: 2px 6px; padding: 2px 5px;
border-radius: 4px; border-radius: 4px;
color: var(--text-muted); color: var(--text-muted);
font-size: var(--font-ui-small); font-size: var(--font-ui-small);
transition: color 80ms, background-color 80ms;
box-shadow: none;
} }
.waypoint-calendar-today-group button:hover { .waypoint-calendar-today-group button:hover {
@@ -121,30 +128,34 @@
.waypoint-calendar table { .waypoint-calendar table {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: separate;
border-spacing: 2px 3px;
} }
.waypoint-calendar th, .waypoint-calendar th,
.waypoint-calendar td { .waypoint-calendar td {
text-align: center; text-align: center;
padding: 4px 2px; padding: 0;
font-size: var(--font-ui-small); font-size: var(--font-ui-small);
line-height: 1.6;
} }
.waypoint-calendar th { .waypoint-calendar th {
padding-bottom: 2px;
font-weight: var(--font-medium); font-weight: var(--font-medium);
color: var(--text-faint); color: var(--text-faint);
font-size: calc(var(--font-ui-small) * 0.85); font-size: calc(var(--font-ui-small) * 0.78);
letter-spacing: 0.04em;
text-transform: uppercase;
} }
.waypoint-calendar .waypoint-weeknum { .waypoint-calendar .waypoint-weeknum {
font-size: calc(var(--font-ui-small) * 0.75); width: 18px;
font-size: calc(var(--font-ui-small) * 0.72);
color: var(--text-faint); color: var(--text-faint);
font-weight: var(--font-light); font-weight: var(--font-light);
cursor: var(--cursor-link, pointer); cursor: var(--cursor-link, pointer);
padding: 2px 0;
border-radius: 4px; border-radius: 4px;
transition: color 80ms, background-color 80ms;
} }
.waypoint-calendar .waypoint-weeknum:hover { .waypoint-calendar .waypoint-weeknum:hover {
@@ -154,42 +165,71 @@
.waypoint-calendar .waypoint-day { .waypoint-calendar .waypoint-day {
cursor: var(--cursor-link, pointer); cursor: var(--cursor-link, pointer);
min-height: var(--wp-cal-cell-size, 32px); height: var(--wp-cal-cell-size, 32px);
padding: 2px 0; box-sizing: border-box;
padding: 2px 0 7px;
border-radius: 6px; border-radius: 6px;
position: relative; position: relative;
width: 100%; width: 100%;
vertical-align: middle; vertical-align: middle;
transition: color 80ms, background-color 80ms, box-shadow 80ms;
} }
.waypoint-calendar .waypoint-day:hover { .waypoint-calendar .waypoint-day:hover {
background-color: var(--background-modifier-active-hover); background-color: var(--background-modifier-hover);
} }
.waypoint-calendar .waypoint-day.other-month { .waypoint-calendar .waypoint-day.other-month {
opacity: 0.35; color: var(--text-faint);
opacity: 0.5;
} }
.waypoint-calendar .waypoint-day.today { .waypoint-calendar .waypoint-day.today {
color: var(--text-accent); color: var(--text-accent);
border: 1px solid var(--text-accent);
font-weight: var(--font-semibold); font-weight: var(--font-semibold);
box-shadow: inset 0 0 0 1px var(--text-accent);
} }
.waypoint-calendar .waypoint-day.today:hover {
background-color: var(--background-modifier-active-hover);
}
/* Single-dot mode preserves the pre-date-systems indicator. */
.waypoint-calendar .waypoint-day.has-note::after { .waypoint-calendar .waypoint-day.has-note::after {
content: ''; content: '';
display: block; position: absolute;
left: 50%;
bottom: 3px;
width: 4px; width: 4px;
height: 4px; height: 4px;
border-radius: 50%; border-radius: 50%;
background-color: var(--text-faint); background-color: var(--text-faint);
margin: 0 auto; transform: translateX(-50%);
} }
.waypoint-calendar .waypoint-day.today.has-note::after { .waypoint-calendar .waypoint-day.today.has-note::after {
background-color: var(--text-accent); background-color: var(--text-accent);
} }
/* Colour-by-system mode: one dot per matching system, in Settings order. */
.waypoint-day-indicators {
position: absolute;
right: 0;
bottom: 3px;
left: 0;
display: flex;
justify-content: center;
gap: 3px;
}
.waypoint-day-indicator {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: var(--waypoint-indicator-color);
box-shadow: 0 0 0 1px var(--background-primary);
}
/* ── Recent Files ── */ /* ── Recent Files ── */
.waypoint-recent-filter { .waypoint-recent-filter {