mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
feat(bases): add MiniCalendar view and fix DateValue conversion
- Add new MiniCalendarBasesView for visualizing notes by date property
- Monthly calendar grid with dot indicators on days with notes
- Configurable date property (file.ctime, note.dateCreated, etc.)
- Fuzzy selector modal for selecting notes on clicked days
- UTC anchor compliance for timezone-independent date handling
- Reuses existing mini-calendar CSS styling
- Fix BasesDataAdapter.convertValueToNative() to properly handle DateValue
- DateValue objects have {date: Date, time: boolean} structure
- Now checks for value.date instanceof Date before legacy methods
- Fixes property-based events in CalendarView as well
- Register MiniCalendar view in bases registration
- View name: "TaskNotes Mini Calendar"
- Default property: file.ctime
- Property filter for date-type properties
Files changed:
- src/bases/MiniCalendarView.ts (new)
- src/bases/BasesDataAdapter.ts (fix DateValue conversion)
- src/bases/registration.ts (register view)
This commit is contained in:
parent
96e01ae58b
commit
15a68ce965
4 changed files with 596 additions and 3 deletions
|
|
@ -10,7 +10,7 @@ TaskNotes ships with a lightweight i18n system that keeps interface copy separat
|
|||
|
||||
## Adding a New Language
|
||||
|
||||
1. Duplicate `src/i18n/resources/en.ts` to a new file with the ISO language code, e.g. `de.ts`.
|
||||
2. Duplicate `src/i18n/resources/en.ts` to a new file with the ISO language code, e.g. `de.ts`.
|
||||
2. Translate only the string values. Keep keys identical so TypeScript’s type-checking continues to work.
|
||||
3. Import the new resource in `src/i18n/index.ts` and add it to `translationResources`.
|
||||
4. (Optional) Add a human-readable label under `common.languages.<code>` so the language shows up nicely in the UI picker.
|
||||
|
|
|
|||
|
|
@ -110,7 +110,13 @@ export class BasesDataAdapter {
|
|||
return result;
|
||||
}
|
||||
|
||||
// DateValue
|
||||
// DateValue - check for date property (more reliable than constructor check)
|
||||
if (value.date instanceof Date) {
|
||||
// Return the date as ISO string for consistency
|
||||
return value.date.toISOString();
|
||||
}
|
||||
|
||||
// DateValue - legacy check with toISOString method
|
||||
if (value.constructor?.name === "DateValue" && value.toISOString) {
|
||||
return value.toISOString();
|
||||
}
|
||||
|
|
|
|||
565
src/bases/MiniCalendarView.ts
Normal file
565
src/bases/MiniCalendarView.ts
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
import { TFile, FuzzySuggestModal, FuzzyMatch, setTooltip } from "obsidian";
|
||||
import TaskNotesPlugin from "../main";
|
||||
import { BasesViewBase } from "./BasesViewBase";
|
||||
import { TaskInfo } from "../types";
|
||||
import { format } from "date-fns";
|
||||
import {
|
||||
formatDateForStorage,
|
||||
getTodayLocal,
|
||||
createUTCDateFromLocalCalendarDate,
|
||||
convertUTCToLocalCalendarDate,
|
||||
createSafeUTCDate,
|
||||
parseDateToUTC,
|
||||
getDatePart,
|
||||
} from "../utils/dateUtils";
|
||||
import { isSameDay } from "../utils/helpers";
|
||||
|
||||
interface NoteEntry {
|
||||
file: TFile;
|
||||
title: string;
|
||||
path: string;
|
||||
dateValue: string; // The date string from the property
|
||||
basesEntry?: any; // Reference to Bases entry for additional data
|
||||
}
|
||||
|
||||
export class MiniCalendarView extends BasesViewBase {
|
||||
type = "tasknotesMiniCalendar";
|
||||
private calendarEl: HTMLElement | null = null;
|
||||
private basesViewContext?: any;
|
||||
|
||||
// View options
|
||||
private dateProperty: string | null = null; // e.g., "note.dueDate", "file.ctime", "note.scheduled"
|
||||
private displayedMonth: number;
|
||||
private displayedYear: number;
|
||||
private selectedDate: Date; // UTC-anchored
|
||||
|
||||
// Data
|
||||
private notesByDate: Map<string, NoteEntry[]> = new Map();
|
||||
private monthCalculationCache: Map<string, { actualMonth: number; dateObj: Date; dateKey: string }> = new Map();
|
||||
|
||||
constructor(controller: any, containerEl: HTMLElement, plugin: TaskNotesPlugin) {
|
||||
super(controller, containerEl, plugin);
|
||||
|
||||
// Initialize with today
|
||||
const todayLocal = getTodayLocal();
|
||||
const todayUTC = createUTCDateFromLocalCalendarDate(todayLocal);
|
||||
this.selectedDate = todayUTC;
|
||||
this.displayedMonth = todayUTC.getUTCMonth();
|
||||
this.displayedYear = todayUTC.getUTCFullYear();
|
||||
}
|
||||
|
||||
setBasesViewContext(context: any): void {
|
||||
this.basesViewContext = context;
|
||||
(this.dataAdapter as any).basesView = context;
|
||||
|
||||
// Read view options
|
||||
this.readViewOptions();
|
||||
}
|
||||
|
||||
private readViewOptions(): void {
|
||||
if (!this.basesViewContext?.config) return;
|
||||
|
||||
const config = this.basesViewContext.config;
|
||||
if (typeof config.get !== 'function') return;
|
||||
|
||||
try {
|
||||
this.dateProperty = config.get('dateProperty') || 'file.ctime';
|
||||
} catch (e) {
|
||||
console.error("[TaskNotes][MiniCalendarView] Error reading view options:", e);
|
||||
}
|
||||
}
|
||||
|
||||
async render(): Promise<void> {
|
||||
if (!this.calendarEl || !this.rootElement) return;
|
||||
if (!this.basesViewContext?.data?.data) return;
|
||||
|
||||
try {
|
||||
// Clear calendar
|
||||
this.calendarEl.empty();
|
||||
|
||||
// Use raw Bases data (has getValue() method)
|
||||
const basesEntries = this.basesViewContext.data.data;
|
||||
|
||||
// Index notes by date
|
||||
this.indexNotesByDate(basesEntries);
|
||||
|
||||
// Render calendar grid
|
||||
this.renderCalendarControls();
|
||||
this.renderCalendarGrid();
|
||||
} catch (error: any) {
|
||||
console.error("[TaskNotes][MiniCalendarView] Error rendering:", error);
|
||||
this.renderError(error);
|
||||
}
|
||||
}
|
||||
|
||||
private indexNotesByDate(dataItems: any[]): void {
|
||||
this.notesByDate.clear();
|
||||
|
||||
if (!this.dateProperty) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const item of dataItems) {
|
||||
try {
|
||||
const file = item.file;
|
||||
if (!file) continue;
|
||||
|
||||
// Get date value from the configured property
|
||||
const dateValue = this.getDateValueFromProperty(item, this.dateProperty);
|
||||
if (!dateValue) continue;
|
||||
|
||||
// Extract date part (handles both date-only and datetime strings)
|
||||
const dateKey = getDatePart(dateValue);
|
||||
if (!dateKey) continue;
|
||||
|
||||
// Create note entry
|
||||
const noteEntry: NoteEntry = {
|
||||
file: file,
|
||||
title: file.basename || file.name,
|
||||
path: file.path,
|
||||
dateValue: dateValue,
|
||||
basesEntry: item,
|
||||
};
|
||||
|
||||
// Add to map
|
||||
if (!this.notesByDate.has(dateKey)) {
|
||||
this.notesByDate.set(dateKey, []);
|
||||
}
|
||||
this.notesByDate.get(dateKey)!.push(noteEntry);
|
||||
} catch (error) {
|
||||
console.warn("[TaskNotes][MiniCalendarView] Error indexing note:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getDateValueFromProperty(item: any, propertyId: string): string | null {
|
||||
try {
|
||||
// Use BasesDataAdapter to get the property value (handles all Bases Value types)
|
||||
const value = this.dataAdapter.getPropertyValue(item, propertyId);
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now we have a native JavaScript value
|
||||
let dateString: string | null = null;
|
||||
|
||||
// Handle different value types
|
||||
if (typeof value === 'string') {
|
||||
// Could be an ISO date string, date-only string, or filename
|
||||
// First, try to parse as date
|
||||
const testDate = new Date(value);
|
||||
if (!isNaN(testDate.getTime())) {
|
||||
dateString = getDatePart(value);
|
||||
} else {
|
||||
// Maybe it's a filename with embedded date
|
||||
const dateMatch = value.match(/(\d{4}-\d{2}-\d{2})/);
|
||||
if (dateMatch) {
|
||||
dateString = dateMatch[1];
|
||||
}
|
||||
}
|
||||
} else if (typeof value === 'number') {
|
||||
// Unix timestamp
|
||||
const date = new Date(value);
|
||||
if (!isNaN(date.getTime())) {
|
||||
dateString = format(date, "yyyy-MM-dd");
|
||||
}
|
||||
} else if (value instanceof Date) {
|
||||
// Direct Date object
|
||||
if (!isNaN(value.getTime())) {
|
||||
dateString = format(value, "yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
|
||||
return dateString;
|
||||
} catch (error) {
|
||||
console.warn("[TaskNotes][MiniCalendarView] Error getting date value:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private renderCalendarControls(): void {
|
||||
const controlsContainer = this.calendarEl!.createDiv({ cls: "mini-calendar-view__controls" });
|
||||
const headerContainer = controlsContainer.createDiv({ cls: "mini-calendar-view__header" });
|
||||
|
||||
// Navigation section
|
||||
const navSection = headerContainer.createDiv({ cls: "mini-calendar-view__navigation" });
|
||||
|
||||
// Previous month button
|
||||
const prevButton = navSection.createEl("button", {
|
||||
text: "‹",
|
||||
cls: "mini-calendar-view__nav-button mini-calendar-view__nav-button--prev tn-btn tn-btn--icon tn-btn--ghost",
|
||||
attr: {
|
||||
"aria-label": "Previous month",
|
||||
title: "Previous month",
|
||||
},
|
||||
});
|
||||
prevButton.addEventListener("click", () => this.navigateToPreviousMonth());
|
||||
|
||||
// Current month display
|
||||
navSection.createDiv({
|
||||
cls: "mini-calendar-view__month-display",
|
||||
text: format(convertUTCToLocalCalendarDate(this.selectedDate), "MMMM yyyy"),
|
||||
});
|
||||
|
||||
// Next month button
|
||||
const nextButton = navSection.createEl("button", {
|
||||
text: "›",
|
||||
cls: "mini-calendar-view__nav-button mini-calendar-view__nav-button--next tn-btn tn-btn--icon tn-btn--ghost",
|
||||
attr: {
|
||||
"aria-label": "Next month",
|
||||
title: "Next month",
|
||||
},
|
||||
});
|
||||
nextButton.addEventListener("click", () => this.navigateToNextMonth());
|
||||
|
||||
// Today button
|
||||
const todayButton = headerContainer.createEl("button", {
|
||||
text: "Today",
|
||||
cls: "mini-calendar-view__today-button tn-btn tn-btn--ghost tn-btn--sm",
|
||||
attr: {
|
||||
"aria-label": "Go to today",
|
||||
title: "Go to today",
|
||||
},
|
||||
});
|
||||
todayButton.addEventListener("click", () => this.navigateToToday());
|
||||
}
|
||||
|
||||
private renderCalendarGrid(): void {
|
||||
const gridContainer = this.calendarEl!.createDiv({ cls: "mini-calendar-view__grid-container" });
|
||||
|
||||
// Get current month/year from displayed date
|
||||
const currentMonth = this.displayedMonth;
|
||||
const currentYear = this.displayedYear;
|
||||
|
||||
// Get first and last day of month
|
||||
const firstDayOfMonth = new Date(Date.UTC(currentYear, currentMonth, 1));
|
||||
const lastDayOfMonth = new Date(Date.UTC(currentYear, currentMonth + 1, 0));
|
||||
|
||||
const firstDaySetting = this.plugin.settings.calendarViewSettings.firstDay || 0;
|
||||
const firstDayOfWeek = (firstDayOfMonth.getUTCDay() - firstDaySetting + 7) % 7;
|
||||
|
||||
// Create calendar grid
|
||||
const calendarGrid = gridContainer.createDiv({
|
||||
cls: "mini-calendar-view__grid",
|
||||
attr: {
|
||||
role: "grid",
|
||||
"aria-label": `Calendar for ${format(convertUTCToLocalCalendarDate(new Date(Date.UTC(currentYear, currentMonth, 1))), "MMMM yyyy")}`,
|
||||
},
|
||||
});
|
||||
|
||||
// Day names header
|
||||
const calendarHeader = calendarGrid.createDiv({
|
||||
cls: "mini-calendar-view__grid-header",
|
||||
attr: { role: "row" },
|
||||
});
|
||||
|
||||
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
const reorderedDayNames = [
|
||||
...dayNames.slice(firstDaySetting),
|
||||
...dayNames.slice(0, firstDaySetting),
|
||||
];
|
||||
|
||||
reorderedDayNames.forEach((dayName) => {
|
||||
calendarHeader.createDiv({
|
||||
text: dayName,
|
||||
cls: "mini-calendar-view__day-header",
|
||||
attr: { role: "columnheader", "aria-label": dayName },
|
||||
});
|
||||
});
|
||||
|
||||
// Calculate grid layout
|
||||
const daysFromPrevMonth = firstDayOfWeek;
|
||||
const totalCells = 42; // 6 rows of 7 days
|
||||
const daysThisMonth = lastDayOfMonth.getUTCDate();
|
||||
const daysFromNextMonth = totalCells - daysThisMonth - daysFromPrevMonth;
|
||||
const lastDayOfPrevMonth = new Date(Date.UTC(currentYear, currentMonth, 0)).getUTCDate();
|
||||
|
||||
// Today for comparison
|
||||
const todayLocal = getTodayLocal();
|
||||
const today = createUTCDateFromLocalCalendarDate(todayLocal);
|
||||
|
||||
// Render days
|
||||
let currentWeekRow = calendarGrid.createDiv({
|
||||
cls: "mini-calendar-view__week",
|
||||
attr: { role: "row" },
|
||||
});
|
||||
|
||||
// Previous month days
|
||||
for (let i = 0; i < daysFromPrevMonth; i++) {
|
||||
const dayNum = lastDayOfPrevMonth - daysFromPrevMonth + i + 1;
|
||||
const dayDate = new Date(Date.UTC(currentYear, currentMonth - 1, dayNum));
|
||||
this.renderDay(currentWeekRow, dayDate, dayNum, true);
|
||||
}
|
||||
|
||||
// Current month days
|
||||
for (let i = 1; i <= daysThisMonth; i++) {
|
||||
if ((i + daysFromPrevMonth - 1) % 7 === 0 && i > 1) {
|
||||
currentWeekRow = calendarGrid.createDiv({
|
||||
cls: "mini-calendar-view__week",
|
||||
attr: { role: "row" },
|
||||
});
|
||||
}
|
||||
|
||||
const dayDate = new Date(Date.UTC(currentYear, currentMonth, i));
|
||||
this.renderDay(currentWeekRow, dayDate, i, false);
|
||||
}
|
||||
|
||||
// Next month days
|
||||
for (let i = 1; i <= daysFromNextMonth; i++) {
|
||||
if ((i + daysFromPrevMonth + daysThisMonth - 1) % 7 === 0 && i > 1) {
|
||||
currentWeekRow = calendarGrid.createDiv({
|
||||
cls: "mini-calendar-view__week",
|
||||
attr: { role: "row" },
|
||||
});
|
||||
}
|
||||
|
||||
const dayDate = new Date(Date.UTC(currentYear, currentMonth + 1, i));
|
||||
this.renderDay(currentWeekRow, dayDate, i, true);
|
||||
}
|
||||
}
|
||||
|
||||
private renderDay(weekRow: HTMLElement, dayDate: Date, dayNum: number, isOutsideMonth: boolean): void {
|
||||
const todayLocal = getTodayLocal();
|
||||
const today = createUTCDateFromLocalCalendarDate(todayLocal);
|
||||
|
||||
const isToday = isSameDay(dayDate, today);
|
||||
const isSelected = isSameDay(dayDate, this.selectedDate);
|
||||
|
||||
let classNames = "mini-calendar-view__day";
|
||||
if (isToday) classNames += " mini-calendar-view__day--today";
|
||||
if (isSelected) classNames += " mini-calendar-view__day--selected";
|
||||
if (isOutsideMonth) classNames += " mini-calendar-view__day--outside-month";
|
||||
|
||||
const dayEl = weekRow.createDiv({
|
||||
cls: classNames,
|
||||
text: dayNum.toString(),
|
||||
attr: {
|
||||
role: "gridcell",
|
||||
tabindex: isSelected ? "0" : "-1",
|
||||
"aria-label": format(convertUTCToLocalCalendarDate(dayDate), "EEEE, MMMM d, yyyy") + (isToday ? " (Today)" : ""),
|
||||
"aria-selected": isSelected ? "true" : "false",
|
||||
"aria-current": isToday ? "date" : null,
|
||||
},
|
||||
});
|
||||
|
||||
// Add dot indicator if notes exist for this date
|
||||
const dateKey = formatDateForStorage(dayDate);
|
||||
const notesForDay = this.notesByDate.get(dateKey);
|
||||
|
||||
if (notesForDay && notesForDay.length > 0) {
|
||||
const indicator = dayEl.createDiv({ cls: "note-indicator" });
|
||||
|
||||
// Style based on count
|
||||
if (notesForDay.length >= 5) {
|
||||
indicator.addClass("many-notes");
|
||||
dayEl.addClass("mini-calendar-view__day--has-notes-many");
|
||||
} else if (notesForDay.length >= 2) {
|
||||
indicator.addClass("some-notes");
|
||||
dayEl.addClass("mini-calendar-view__day--has-notes-some");
|
||||
} else {
|
||||
indicator.addClass("few-notes");
|
||||
dayEl.addClass("mini-calendar-view__day--has-notes-few");
|
||||
}
|
||||
|
||||
setTooltip(indicator, `${notesForDay.length} note${notesForDay.length > 1 ? "s" : ""}`, {
|
||||
placement: "top",
|
||||
});
|
||||
}
|
||||
|
||||
// Click handler - select date or show fuzzy selector
|
||||
dayEl.addEventListener("click", () => {
|
||||
this.handleDayClick(dayDate);
|
||||
});
|
||||
|
||||
// Keyboard handler
|
||||
dayEl.addEventListener("keydown", (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
this.handleDayClick(dayDate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleDayClick(date: Date): void {
|
||||
// Update selected date
|
||||
this.selectedDate = date;
|
||||
|
||||
// Check if date has notes
|
||||
const dateKey = formatDateForStorage(date);
|
||||
const notesForDay = this.notesByDate.get(dateKey);
|
||||
|
||||
if (notesForDay && notesForDay.length > 0) {
|
||||
// Show fuzzy selector with notes
|
||||
const modal = new NoteSelectionModal(
|
||||
this.plugin.app,
|
||||
this.plugin,
|
||||
notesForDay,
|
||||
(selectedNote) => {
|
||||
if (selectedNote) {
|
||||
// Open the selected note
|
||||
this.plugin.app.workspace.getLeaf(false).openFile(selectedNote.file);
|
||||
}
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
} else {
|
||||
// Just update selection visually
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private navigateToPreviousMonth(): void {
|
||||
const newDate = new Date(this.selectedDate.getTime());
|
||||
newDate.setUTCMonth(this.selectedDate.getUTCMonth() - 1);
|
||||
this.selectedDate = newDate;
|
||||
this.displayedMonth = newDate.getUTCMonth();
|
||||
this.displayedYear = newDate.getUTCFullYear();
|
||||
this.monthCalculationCache.clear();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private navigateToNextMonth(): void {
|
||||
const newDate = new Date(this.selectedDate.getTime());
|
||||
newDate.setUTCMonth(this.selectedDate.getUTCMonth() + 1);
|
||||
this.selectedDate = newDate;
|
||||
this.displayedMonth = newDate.getUTCMonth();
|
||||
this.displayedYear = newDate.getUTCFullYear();
|
||||
this.monthCalculationCache.clear();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private navigateToToday(): void {
|
||||
const todayLocal = getTodayLocal();
|
||||
const todayUTC = createUTCDateFromLocalCalendarDate(todayLocal);
|
||||
this.selectedDate = todayUTC;
|
||||
this.displayedMonth = todayUTC.getUTCMonth();
|
||||
this.displayedYear = todayUTC.getUTCFullYear();
|
||||
this.monthCalculationCache.clear();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
protected setupContainer(): void {
|
||||
super.setupContainer();
|
||||
|
||||
const calendar = document.createElement("div");
|
||||
calendar.className = "mini-calendar-bases-view";
|
||||
this.rootElement?.appendChild(calendar);
|
||||
this.calendarEl = calendar;
|
||||
}
|
||||
|
||||
protected async handleTaskUpdate(task: TaskInfo): Promise<void> {
|
||||
// For mini calendar, refresh on any data change
|
||||
this.debouncedRefresh();
|
||||
}
|
||||
|
||||
private renderError(error: Error): void {
|
||||
if (!this.calendarEl) return;
|
||||
|
||||
const errorEl = document.createElement("div");
|
||||
errorEl.className = "tn-bases-error";
|
||||
errorEl.style.cssText =
|
||||
"padding: 20px; color: #d73a49; background: #ffeaea; border-radius: 4px; margin: 10px 0;";
|
||||
errorEl.textContent = `Error loading mini calendar: ${error.message || "Unknown error"}`;
|
||||
this.calendarEl.appendChild(errorEl);
|
||||
}
|
||||
|
||||
protected cleanup(): void {
|
||||
super.cleanup();
|
||||
this.calendarEl = null;
|
||||
this.notesByDate.clear();
|
||||
this.monthCalculationCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzzy selector modal for notes
|
||||
class NoteSelectionModal extends FuzzySuggestModal<NoteEntry> {
|
||||
private notes: NoteEntry[];
|
||||
private onChooseNote: (note: NoteEntry | null) => void;
|
||||
private plugin: TaskNotesPlugin;
|
||||
|
||||
constructor(
|
||||
app: any,
|
||||
plugin: TaskNotesPlugin,
|
||||
notes: NoteEntry[],
|
||||
onChooseNote: (note: NoteEntry | null) => void
|
||||
) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.notes = notes;
|
||||
this.onChooseNote = onChooseNote;
|
||||
|
||||
this.setPlaceholder("Select a note to open");
|
||||
this.setInstructions([
|
||||
{ command: "↑↓", purpose: "Navigate" },
|
||||
{ command: "↵", purpose: "Open note" },
|
||||
{ command: "esc", purpose: "Dismiss" },
|
||||
]);
|
||||
}
|
||||
|
||||
getItems(): NoteEntry[] {
|
||||
// Sort by title
|
||||
return this.notes.sort((a, b) => a.title.localeCompare(b.title));
|
||||
}
|
||||
|
||||
getItemText(note: NoteEntry): string {
|
||||
return note.title;
|
||||
}
|
||||
|
||||
renderSuggestion(item: FuzzyMatch<NoteEntry>, el: HTMLElement): void {
|
||||
const note = item.item;
|
||||
const container = el.createDiv({ cls: "note-selector-modal__suggestion" });
|
||||
|
||||
// Title
|
||||
container.createDiv({
|
||||
cls: "note-selector-modal__title",
|
||||
text: note.title,
|
||||
});
|
||||
|
||||
// Path (if not same as title)
|
||||
if (note.path !== note.title) {
|
||||
container.createDiv({
|
||||
cls: "note-selector-modal__path",
|
||||
text: note.path,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onChooseItem(item: NoteEntry, evt: MouseEvent | KeyboardEvent): void {
|
||||
this.onChooseNote(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Factory function
|
||||
export function buildMiniCalendarViewFactory(plugin: TaskNotesPlugin) {
|
||||
return function (basesContainer: any, containerEl?: HTMLElement) {
|
||||
const viewContainerEl = containerEl || basesContainer.viewContainerEl;
|
||||
const controller = basesContainer;
|
||||
|
||||
if (!viewContainerEl) {
|
||||
console.error("[TaskNotes][MiniCalendarView] No viewContainerEl found");
|
||||
return { destroy: () => {} } as any;
|
||||
}
|
||||
|
||||
const view = new MiniCalendarView(controller, viewContainerEl, plugin);
|
||||
|
||||
return {
|
||||
load: () => view.load(),
|
||||
unload: () => view.unload(),
|
||||
refresh() { view.render(); },
|
||||
onDataUpdated: function(this: any) {
|
||||
view.setBasesViewContext(this);
|
||||
view.onDataUpdated();
|
||||
},
|
||||
onResize: () => {
|
||||
// Handle resize if needed
|
||||
},
|
||||
getEphemeralState: () => view.getEphemeralState(),
|
||||
setEphemeralState: (state: any) => view.setEphemeralState(state),
|
||||
focus: () => view.focus(),
|
||||
destroy() {
|
||||
view.unload();
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import { requireApiVersion } from "obsidian";
|
|||
import { buildTaskListViewFactory } from "./TaskListView";
|
||||
import { buildKanbanViewFactory } from "./KanbanView";
|
||||
import { buildCalendarViewFactory } from "./CalendarView";
|
||||
import { buildMiniCalendarViewFactory } from "./MiniCalendarView";
|
||||
import { registerBasesView, unregisterBasesView } from "./api";
|
||||
|
||||
/**
|
||||
|
|
@ -385,8 +386,28 @@ export async function registerBasesTaskList(plugin: TaskNotesPlugin): Promise<vo
|
|||
},
|
||||
});
|
||||
|
||||
// Register Mini Calendar view using public API
|
||||
const miniCalendarSuccess = registerBasesView(plugin, "tasknotesMiniCalendar", {
|
||||
name: "TaskNotes Mini Calendar",
|
||||
icon: "calendar-days",
|
||||
factory: buildMiniCalendarViewFactory(plugin),
|
||||
options: () => [
|
||||
{
|
||||
type: "property",
|
||||
key: "dateProperty",
|
||||
displayName: "Date Property",
|
||||
placeholder: "Select property to show on calendar",
|
||||
default: "file.ctime",
|
||||
filter: (prop: string) => {
|
||||
// Show date-type properties from all sources
|
||||
return prop.startsWith("note.") || prop.startsWith("file.") || prop.startsWith("task.");
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Consider it successful if any view registered successfully
|
||||
if (!taskListSuccess && !kanbanSuccess && !calendarSuccess) {
|
||||
if (!taskListSuccess && !kanbanSuccess && !calendarSuccess && !miniCalendarSuccess) {
|
||||
console.debug("[TaskNotes][Bases] Bases plugin not available for registration");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -440,6 +461,7 @@ export function unregisterBasesViews(plugin: TaskNotesPlugin): void {
|
|||
unregisterBasesView(plugin, "tasknotesTaskList");
|
||||
unregisterBasesView(plugin, "tasknotesKanban");
|
||||
unregisterBasesView(plugin, "tasknotesCalendar");
|
||||
unregisterBasesView(plugin, "tasknotesMiniCalendar");
|
||||
} catch (error) {
|
||||
console.error("[TaskNotes][Bases] Error during view unregistration:", error);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue