mirror of
https://github.com/stfrigerio/sqliteDB.git
synced 2026-07-22 05:38:02 +00:00
date navigator
This commit is contained in:
parent
cba7bda90a
commit
9c1020b8d3
19 changed files with 713 additions and 236 deletions
19
main.ts
19
main.ts
|
|
@ -9,11 +9,12 @@ import {
|
|||
import { DBService } from "./src/DBService";
|
||||
import { SQLiteDBSettingTab } from "./src/settingTab";
|
||||
import { inspectTableStructure, convertEntriesInNotes } from "./src/commands";
|
||||
import { processSqlBlock, processSqlChartBlock, renderDatePicker } from "./src/codeblocks";
|
||||
import { processSqlBlock, processSqlChartBlock, DateNavigatorRenderer } from "./src/codeblocks";
|
||||
import { pickTableName } from "./src/helpers";
|
||||
import { SQLiteDBSettings, DEFAULT_SETTINGS } from "./src/types";
|
||||
|
||||
import { injectDatePickerStyles } from "src/styles/datePickerInject";
|
||||
import { injectDateNavigatorStyles, removeDateNavigatorStyles } from './src/styles/dateNavigationInject';
|
||||
|
||||
import { registerHabitCounter } from "./src/webcomponents/HabitCounter/registerHabitCounter";
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ export default class SQLiteDBPlugin extends Plugin {
|
|||
await this.openDatabase();
|
||||
|
||||
injectDatePickerStyles();
|
||||
injectDateNavigatorStyles();
|
||||
|
||||
this.registerMarkdownPostProcessor((el, ctx) => {
|
||||
registerHabitCounter(el, this.dbService);
|
||||
|
|
@ -73,18 +75,19 @@ export default class SQLiteDBPlugin extends Plugin {
|
|||
}
|
||||
);
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor(
|
||||
"date-picker",
|
||||
async (source: string, el: HTMLElement) => {
|
||||
renderDatePicker(el, this.app);
|
||||
}
|
||||
);
|
||||
this.registerMarkdownCodeBlockProcessor(
|
||||
"date-header",
|
||||
(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
ctx.addChild(new DateNavigatorRenderer(el, this.app));
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
this.addSettingTab(new SQLiteDBSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
console.log("Unloading SQLiteDBPlugin...");
|
||||
removeDateNavigatorStyles();
|
||||
}
|
||||
|
||||
private async openDatabase(forceReload = true) {
|
||||
|
|
|
|||
|
|
@ -33,24 +33,19 @@ export class DBService {
|
|||
// Only reload if forced or not already loaded
|
||||
if (!this.db || forceReload) {
|
||||
try {
|
||||
console.log("DBService: Initializing SQL.js and loading DB...");
|
||||
this.SQL = await initSqlJs({
|
||||
locateFile: (file) => `${basePath}/${this.app.vault.configDir}/plugins/sqlite-db/${file}`,
|
||||
});
|
||||
|
||||
console.log(`DBService: Reading DB file from: ${settings.dbFilePath}`);
|
||||
const fileBuffer = readFileSync(settings.dbFilePath);
|
||||
this.db = new this.SQL.Database(fileBuffer);
|
||||
console.log("DBService: Database loaded into memory.");
|
||||
|
||||
// Check if we have at least one table
|
||||
const result = this.db.exec("SELECT name FROM sqlite_master WHERE type='table' LIMIT 1;");
|
||||
if (result?.[0]?.values?.[0]?.[0]) {
|
||||
new Notice(`DB Loaded.`);
|
||||
console.log("DBService: DB Loaded successfully.");
|
||||
} else {
|
||||
new Notice("DB Loaded, but found no tables.");
|
||||
console.log("DBService: DB Loaded, but found no tables.");
|
||||
}
|
||||
return true; // Indicate success
|
||||
} catch (err) {
|
||||
|
|
@ -61,8 +56,7 @@ export class DBService {
|
|||
return false; // Indicate failure
|
||||
}
|
||||
} else {
|
||||
console.log("DBService: Database already loaded.");
|
||||
return true; // Already loaded
|
||||
return true; // Already loaded
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import { App } from "obsidian";
|
||||
import { PluginState } from "src/pluginState";
|
||||
import { DateNavigatorOptions, DateNavigatorDOMElements, NavigationPeriod } from "./dateNavigator.types";
|
||||
import { createPrevHandler, createNextHandler, createOpenModalHandler } from "./eventHandlers/dateNavigationHandlers";
|
||||
import { buildDateNavigatorDOM, updateDateNavigatorDisplay } from "./dom/buildDateNavigatorDOM";
|
||||
|
||||
/**
|
||||
* Creates and manages the interactive Date Navigator header.
|
||||
* Uses pluginState to get/set the current date.
|
||||
* Updates its display when the date changes.
|
||||
*/
|
||||
export class DateNavigator {
|
||||
private app: App;
|
||||
private pluginState: PluginState;
|
||||
private containerEl: HTMLElement;
|
||||
private period: NavigationPeriod;
|
||||
private elements: DateNavigatorDOMElements | null = null;
|
||||
|
||||
constructor(options: DateNavigatorOptions) {
|
||||
this.app = options.app;
|
||||
this.pluginState = options.pluginState;
|
||||
this.containerEl = options.containerEl;
|
||||
this.period = options.period ?? 'day'; // Default to day navigation
|
||||
|
||||
if (!this.app || !this.pluginState || !this.containerEl) {
|
||||
throw new Error("DateNavigator missing required options (app, pluginState, containerEl).");
|
||||
}
|
||||
|
||||
this._buildUI();
|
||||
}
|
||||
|
||||
/** Builds the initial UI */
|
||||
private _buildUI(): void {
|
||||
//~ Create bound handlers first
|
||||
const handlers = {
|
||||
handlePrev: createPrevHandler(this.pluginState, this.period, this.app, this._updateDisplay),
|
||||
handleNext: createNextHandler(this.pluginState, this.period, this.app, this._updateDisplay),
|
||||
handleOpenModal: createOpenModalHandler(this.pluginState, this.app, this._updateDisplay),
|
||||
};
|
||||
//~ Build DOM and store element references
|
||||
this.elements = buildDateNavigatorDOM(this.containerEl, this.pluginState.selectedDate, handlers);
|
||||
}
|
||||
|
||||
/** Updates the displayed date. Bound method for callbacks. */
|
||||
private _updateDisplay = (newIsoDate: string): void => {
|
||||
if (this.elements?.dateDisplay) {
|
||||
updateDateNavigatorDisplay(this.elements.dateDisplay, newIsoDate);
|
||||
} else {
|
||||
console.error("[DateNavigator] Cannot update display, dateDisplay element reference is missing.");
|
||||
}
|
||||
};
|
||||
|
||||
/** Cleans up the component (e.g., remove listeners, clear container) */
|
||||
public destroy(): void {
|
||||
this.containerEl.empty();
|
||||
this.elements = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// src/components/dateNavigator/dateNavigator.types.ts
|
||||
import { App } from "obsidian";
|
||||
import { pluginState } from "src/pluginState";
|
||||
|
||||
//? Defines the period types the navigator can handle (currently only 'day')
|
||||
export type NavigationPeriod = 'day'; //todo: Add 'week', 'month', 'year' later
|
||||
|
||||
//? Configuration options for the DateNavigator
|
||||
export interface DateNavigatorOptions {
|
||||
app: App;
|
||||
pluginState: typeof pluginState;
|
||||
containerEl: HTMLElement; // Where to render the navigator
|
||||
period?: NavigationPeriod; // Defaults to 'day'
|
||||
dateFormat?: string; // todo: Allow custom date formats later
|
||||
}
|
||||
|
||||
//? References to the DOM elements created by the navigator builder
|
||||
export interface DateNavigatorDOMElements {
|
||||
wrapper: HTMLElement;
|
||||
navRow: HTMLElement;
|
||||
prevButton: HTMLButtonElement;
|
||||
nextButton: HTMLButtonElement;
|
||||
dateDisplay: HTMLElement; // The H1 or span showing the date
|
||||
openModalButton: HTMLButtonElement;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// src/components/dateNavigator/dom/buildDateNavigatorDOM.ts
|
||||
import { formatDateForDisplay, parseDateISO } from "../../datePicker/datePickerUtils";
|
||||
import type { DateNavigatorDOMElements } from "../dateNavigator.types"; // Type only
|
||||
|
||||
//? Structure for the handlers needed by the builder
|
||||
export interface DateNavigatorHandlers {
|
||||
handlePrev: () => void;
|
||||
handleNext: () => void;
|
||||
handleOpenModal: () => void;
|
||||
}
|
||||
|
||||
/** //? Builds the DOM for the date navigator header. */
|
||||
export function buildDateNavigatorDOM(
|
||||
containerEl: HTMLElement,
|
||||
initialIsoDate: string,
|
||||
handlers: DateNavigatorHandlers
|
||||
): DateNavigatorDOMElements {
|
||||
containerEl.empty(); // Clear container first
|
||||
const wrapper = containerEl.createDiv({ cls: "date-navigator-wrapper" });
|
||||
const navRow = wrapper.createDiv({ cls: "date-nav-row" });
|
||||
|
||||
// --- Navigation Buttons ---
|
||||
const prevButton = navRow.createEl("button", { text: "←", cls: "clickable-icon date-nav-button date-nav-prev" });
|
||||
prevButton.setAttribute("aria-label", "Previous Day"); //todo: Make label dynamic based on period
|
||||
prevButton.addEventListener("click", handlers.handlePrev);
|
||||
|
||||
// --- Date Display (H1) ---
|
||||
const dateDisplay = navRow.createEl("h1", { cls: "date-navigator-display" });
|
||||
const initialDateObj = parseDateISO(initialIsoDate);
|
||||
dateDisplay.textContent = initialDateObj ? formatDateForDisplay(initialDateObj) : "Invalid Date";
|
||||
|
||||
const nextButton = navRow.createEl("button", { text: "→", cls: "clickable-icon date-nav-button date-nav-next" });
|
||||
nextButton.setAttribute("aria-label", "Next Day"); //todo: Make label dynamic
|
||||
nextButton.addEventListener("click", handlers.handleNext);
|
||||
|
||||
// --- Open Modal Button ---
|
||||
//? Placed below the H1 for structure
|
||||
const openModalButton = wrapper.createEl("button", { text: "Select Date", cls: "date-nav-open-modal" });
|
||||
openModalButton.addEventListener("click", handlers.handleOpenModal);
|
||||
|
||||
return { wrapper, navRow, prevButton, nextButton, dateDisplay, openModalButton };
|
||||
}
|
||||
|
||||
/** Updates the displayed date text in the navigator. */
|
||||
export function updateDateNavigatorDisplay(dateDisplayElement: HTMLElement, newIsoDate: string): void {
|
||||
const dateObj = parseDateISO(newIsoDate);
|
||||
const displayString = dateObj ? formatDateForDisplay(dateObj) : "Invalid Date";
|
||||
if (dateDisplayElement) {
|
||||
dateDisplayElement.textContent = displayString;
|
||||
} else {
|
||||
console.error("[DateNavDOM] Cannot update display, element not found.");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// src/components/dateNavigator/eventHandlers/dateNavigationHandlers.ts
|
||||
import { App } from "obsidian";
|
||||
import { DatePickerModal } from "../../datePicker/DatePickerModal";
|
||||
import { PluginState } from "src/pluginState";
|
||||
import { parseDateISO, formatDateISO } from "../../datePicker/datePickerUtils";
|
||||
import { NavigationPeriod } from "../dateNavigator.types";
|
||||
|
||||
/** Updates the global plugin state with the new date and triggers necessary updates */
|
||||
function updateGlobalDate(newDate: Date, pluginState: PluginState, app: App): void {
|
||||
const newIsoDate = formatDateISO(newDate);
|
||||
if (pluginState.selectedDate !== newIsoDate) {
|
||||
pluginState.selectedDate = newIsoDate;
|
||||
// Optional: Force re-render if needed, maybe make this configurable
|
||||
// app.workspace.getActiveViewOfType(MarkdownView)?.previewMode?.rerender(true);
|
||||
}
|
||||
//todo: Could add dispatchEvent here for more reactive updates elsewhere
|
||||
}
|
||||
|
||||
/** //? Creates the handler for the "previous" button click */
|
||||
export function createPrevHandler(
|
||||
pluginState: PluginState,
|
||||
period: NavigationPeriod,
|
||||
app: App,
|
||||
//? Callback to update the display text after changing the date
|
||||
updateDisplayCallback: (newIsoDate: string) => void
|
||||
): () => void {
|
||||
return () => {
|
||||
const currentDate = parseDateISO(pluginState.selectedDate) ?? new Date(); // Default to today on parse error
|
||||
switch (period) {
|
||||
case 'day':
|
||||
currentDate.setUTCDate(currentDate.getUTCDate() - 1);
|
||||
break;
|
||||
//todo: Implement 'week', 'month', etc.
|
||||
default:
|
||||
console.warn(`[DateNavHandlers] Unsupported period for prev navigation: ${period}`);
|
||||
return;
|
||||
}
|
||||
updateGlobalDate(currentDate, pluginState, app);
|
||||
updateDisplayCallback(formatDateISO(currentDate)); // Update local display
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates the handler for the "next" button click */
|
||||
export function createNextHandler(
|
||||
pluginState: PluginState,
|
||||
period: NavigationPeriod,
|
||||
app: App,
|
||||
updateDisplayCallback: (newIsoDate: string) => void
|
||||
): () => void {
|
||||
return () => {
|
||||
const currentDate = parseDateISO(pluginState.selectedDate) ?? new Date();
|
||||
//& console.log(`[DateNavHandlers] Next clicked. Current: ${formatDateISO(currentDate)}, Period: ${period}`);
|
||||
switch (period) {
|
||||
case 'day':
|
||||
currentDate.setUTCDate(currentDate.getUTCDate() + 1);
|
||||
break;
|
||||
//todo: Implement 'week', 'month', etc.
|
||||
default:
|
||||
console.warn(`[DateNavHandlers] Unsupported period for next navigation: ${period}`);
|
||||
return;
|
||||
}
|
||||
updateGlobalDate(currentDate, pluginState, app);
|
||||
updateDisplayCallback(formatDateISO(currentDate));
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates the handler for the "open modal" button click */
|
||||
export function createOpenModalHandler(
|
||||
pluginState: PluginState,
|
||||
app: App,
|
||||
updateDisplayCallback: (newIsoDate: string) => void
|
||||
): () => void {
|
||||
return () => {
|
||||
new DatePickerModal(app, pluginState.selectedDate, (newIsoDate) => {
|
||||
//? This is the callback executed when the modal confirms a selection
|
||||
//? No need to call updateGlobalDate here, modal already did via pluginState setter
|
||||
updateDisplayCallback(newIsoDate);
|
||||
}).open();
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import { Modal, App, Notice } from "obsidian";
|
||||
import { buildDatePickerHeader } from "./dom/buildDatePickerHeader";
|
||||
import { buildWeekdayHeaders } from "./dom/buildWeekdayHeaders";
|
||||
import { buildCalendarGrid } from "./dom/buildCalendarGrid";
|
||||
import { buildDatePickerFooter } from "./dom/buildDatePickerFooter";
|
||||
import { formatDateISO, parseDateISO } from "./datePickerUtils";
|
||||
|
||||
export class DatePickerModal extends Modal {
|
||||
private selectedDateObj: Date; //? Internal state as Date object
|
||||
private currentDisplayMonth: Date; //? Month being displayed in the calendar
|
||||
private today: Date; //? Today's date (normalized)
|
||||
|
||||
//? Callback to notify the parent component of the selection
|
||||
private onSelectCallback: (newIsoDate: string) => void;
|
||||
|
||||
constructor(app: App, currentIsoDate: string, onSelect: (newIsoDate: string) => void) {
|
||||
super(app);
|
||||
this.onSelectCallback = onSelect;
|
||||
|
||||
const initialDate = parseDateISO(currentIsoDate);
|
||||
//? Ensure selectedDateObj is always a valid Date object, default to today if parse fails
|
||||
this.selectedDateObj = initialDate && !isNaN(initialDate.getTime()) ? initialDate : new Date();
|
||||
//? Set time to 00:00:00 UTC to compare dates easily
|
||||
this.selectedDateObj.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
this.today = new Date();
|
||||
this.today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
//? Start display month from the selected date's month
|
||||
this.currentDisplayMonth = new Date(Date.UTC(this.selectedDateObj.getUTCFullYear(), this.selectedDateObj.getUTCMonth(), 1));
|
||||
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
this.contentEl.addClass("custom-datepicker-modal-content");
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty(); // Clear previous content
|
||||
|
||||
// --- Build Header ---
|
||||
const headerEl = buildDatePickerHeader(
|
||||
this.currentDisplayMonth,
|
||||
this._handlePrevMonth, // Pass handler references
|
||||
this._handleNextMonth
|
||||
);
|
||||
contentEl.appendChild(headerEl);
|
||||
|
||||
// --- Build Weekday Headers ---
|
||||
const weekdaysEl = buildWeekdayHeaders(true); // Include week number header
|
||||
contentEl.appendChild(weekdaysEl);
|
||||
|
||||
// --- Build Calendar Grid ---
|
||||
const gridEl = buildCalendarGrid(
|
||||
this.currentDisplayMonth,
|
||||
this.selectedDateObj,
|
||||
this.today,
|
||||
this._handleDateSelect // Pass date selection handler
|
||||
);
|
||||
contentEl.appendChild(gridEl);
|
||||
|
||||
// --- Build Footer ---
|
||||
const footerEl = buildDatePickerFooter(
|
||||
this.selectedDateObj,
|
||||
this._confirmSelection // Pass confirmation handler
|
||||
);
|
||||
contentEl.appendChild(footerEl);
|
||||
}
|
||||
|
||||
// --- Handlers passed to builders ---
|
||||
|
||||
private _handlePrevMonth = (): void => {
|
||||
this.currentDisplayMonth.setUTCMonth(this.currentDisplayMonth.getUTCMonth() - 1);
|
||||
this.render(); // Re-render the whole modal
|
||||
};
|
||||
|
||||
private _handleNextMonth = (): void => {
|
||||
this.currentDisplayMonth.setUTCMonth(this.currentDisplayMonth.getUTCMonth() + 1);
|
||||
this.render(); // Re-render the whole modal
|
||||
};
|
||||
|
||||
private _handleDateSelect = (selected: Date): void => {
|
||||
this.selectedDateObj = selected; // Update internal state
|
||||
//? Re-render to update selection highlight and footer display
|
||||
//? Alternatively, could update only specific elements for performance
|
||||
this.render();
|
||||
};
|
||||
|
||||
private _confirmSelection = (): void => {
|
||||
const isoDate = formatDateISO(this.selectedDateObj);
|
||||
this.onSelectCallback(isoDate); // Notify parent component
|
||||
new Notice(`Date set to ${isoDate}`);
|
||||
this.close();
|
||||
};
|
||||
|
||||
onClose() {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/** Calculates the ISO 8601 week number for a given date. */
|
||||
export function getISOWeekNumber(date: Date): number {
|
||||
const temp = new Date(date.getTime());
|
||||
temp.setHours(0, 0, 0, 0);
|
||||
// Thursday in current week decides the year.
|
||||
temp.setDate(temp.getDate() + 3 - ((temp.getDay() + 6) % 7));
|
||||
const week1 = new Date(temp.getFullYear(), 0, 4);
|
||||
return (
|
||||
1 +
|
||||
Math.round(
|
||||
((temp.getTime() - week1.getTime()) / 86400000 - 3 + ((week1.getDay() + 6) % 7)) / 7 // Adjusted calculation slightly
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** Formats a Date object into YYYY-MM-DD string */
|
||||
export function formatDateISO(date: Date): string {
|
||||
if (!date || isNaN(date.getTime())) {
|
||||
return ""; //? Handle invalid date
|
||||
}
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/** Parses a YYYY-MM-DD string into a Date object (at UTC midnight) */
|
||||
export function parseDateISO(dateString: string): Date | null {
|
||||
if (!dateString || !/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
|
||||
return null;
|
||||
}
|
||||
//? Parse as UTC to avoid timezone issues affecting the date part
|
||||
const parts = dateString.split('-').map(Number);
|
||||
const date = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2]));
|
||||
if (isNaN(date.getTime())) {
|
||||
return null;
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/** Formats a date for display (e.g., "January 15, 2024") */
|
||||
export function formatDateForDisplay(date: Date): string {
|
||||
if (!date || isNaN(date.getTime())) return "Invalid Date";
|
||||
return date.toLocaleDateString(undefined, { // Use user's locale
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
// timeZone: 'UTC'
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import { getISOWeekNumber } from "../datePickerUtils";
|
||||
|
||||
/**
|
||||
* Builds the main grid of days for the calendar month.
|
||||
* @param currentDisplayMonth - The month/year being displayed (Date object, 1st of month UTC).
|
||||
* @param selectedDateObj - The currently selected date (Date object, normalized to UTC midnight).
|
||||
* @param today - Today's date (Date object, normalized to UTC midnight).
|
||||
* @param onDateSelect - Callback function executed when a day button is clicked, passing the selected Date object.
|
||||
* @returns The HTMLElement representing the calendar grid.
|
||||
*/
|
||||
export function buildCalendarGrid(
|
||||
currentDisplayMonth: Date,
|
||||
selectedDateObj: Date,
|
||||
today: Date,
|
||||
onDateSelect: (selected: Date) => void
|
||||
): HTMLElement {
|
||||
const grid = document.createElement("div");
|
||||
grid.className = "calendar-grid"; // Grid container
|
||||
|
||||
const year = currentDisplayMonth.getUTCFullYear();
|
||||
const month = currentDisplayMonth.getUTCMonth();
|
||||
|
||||
//? Day of the week for the 1st of the month (0=Sun, 6=Sat). Use UTC methods.
|
||||
const firstDayWeekday = new Date(Date.UTC(year, month, 1)).getUTCDay();
|
||||
//? Calculate offset for Monday start (0=Mon, 6=Sun)
|
||||
const startOffset = (firstDayWeekday + 6) % 7;
|
||||
|
||||
//? Last day of the *current* month. Use UTC month+1, day 0.
|
||||
const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
||||
|
||||
let dayCounter = 1 - startOffset; // Start day counter including offset for leading empty cells
|
||||
let currentWeekNumber = -1; // Track week number to avoid duplicates per row
|
||||
|
||||
while (dayCounter <= daysInMonth) {
|
||||
// --- Add Week Number Cell (at the start of each visual row) ---
|
||||
const dateForWeekNum = new Date(Date.UTC(year, month, dayCounter > 0 ? dayCounter : 1));
|
||||
const weekNumber = getISOWeekNumber(dateForWeekNum);
|
||||
|
||||
//? Add week number cell at the start of grid row (every 8th element: 1 week# + 7 days)
|
||||
if (grid.children.length % 8 === 0) {
|
||||
const weekEl = document.createElement("div");
|
||||
weekEl.className = "week-number";
|
||||
weekEl.textContent = `${String(weekNumber).padStart(2, "0")}`;
|
||||
grid.appendChild(weekEl);
|
||||
currentWeekNumber = weekNumber;
|
||||
}
|
||||
|
||||
// --- Add 7 Day Cells for the current week ---
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const cell = document.createElement("div");
|
||||
cell.className = "calendar-day-cell"; // Container for potential button
|
||||
|
||||
if (dayCounter < 1 || dayCounter > daysInMonth) {
|
||||
//? Empty cell (before start or after end of month)
|
||||
cell.classList.add("empty");
|
||||
} else {
|
||||
//? Valid day within the month
|
||||
const currentDate = new Date(Date.UTC(year, month, dayCounter));
|
||||
//? No need to normalize time, UTC dates are already at midnight
|
||||
|
||||
const dayButton = document.createElement("button");
|
||||
dayButton.textContent = String(dayCounter);
|
||||
dayButton.className = "calendar-day";
|
||||
dayButton.setAttribute("aria-label", currentDate.toLocaleDateString(undefined, { // Use local format for aria-label
|
||||
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC'
|
||||
}));
|
||||
|
||||
//? Check if this is the currently selected date
|
||||
if (currentDate.getTime() === selectedDateObj.getTime()) {
|
||||
dayButton.classList.add("selected");
|
||||
dayButton.setAttribute("aria-selected", "true");
|
||||
} else {
|
||||
dayButton.setAttribute("aria-selected", "false");
|
||||
}
|
||||
|
||||
//? Check if this is today's date
|
||||
if (currentDate.getTime() === today.getTime()) {
|
||||
dayButton.classList.add("today");
|
||||
}
|
||||
|
||||
//? Attach click handler
|
||||
dayButton.onclick = () => {
|
||||
onDateSelect(currentDate); // Pass the Date object back to the modal's handler
|
||||
};
|
||||
|
||||
cell.appendChild(dayButton);
|
||||
}
|
||||
grid.appendChild(cell);
|
||||
dayCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// src/components/datePicker/dom/buildDatePickerFooter.ts
|
||||
import { formatDateForDisplay } from "../datePickerUtils"; // Use consistent display formatting
|
||||
|
||||
/**
|
||||
* Builds the footer section of the date picker modal (Selected Date Display, Confirm Button).
|
||||
* @param selectedDateObj - The currently selected date (Date object).
|
||||
* @param onConfirm - Callback function to execute when the 'Select' button is clicked.
|
||||
* @returns The HTMLElement representing the footer.
|
||||
*/
|
||||
export function buildDatePickerFooter(
|
||||
selectedDateObj: Date,
|
||||
onConfirm: () => void
|
||||
): HTMLElement {
|
||||
const footer = document.createElement("div");
|
||||
footer.className = "calendar-footer";
|
||||
|
||||
// --- Display Currently Selected Date ---
|
||||
const selectedDateDisplay = footer.createEl("span", { cls: "selected-date-display" });
|
||||
selectedDateDisplay.textContent = `Selected: ${formatDateForDisplay(selectedDateObj)}`;
|
||||
|
||||
// --- Confirm Button ---
|
||||
const confirmBtn = footer.createEl("button", { text: "Select", cls: "mod-cta" }); // Use Obsidian's primary button style
|
||||
confirmBtn.onclick = onConfirm; // Assign the provided handler
|
||||
|
||||
return footer;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
/**
|
||||
* Builds the header section of the date picker modal (Prev, Title, Next).
|
||||
* @param currentDisplayMonth - The Date object representing the month/year being displayed.
|
||||
* @param onPrevMonth - Callback function to execute when the 'Previous' button is clicked.
|
||||
* @param onNextMonth - Callback function to execute when the 'Next' button is clicked.
|
||||
* @returns The HTMLElement representing the header.
|
||||
*/
|
||||
export function buildDatePickerHeader(
|
||||
currentDisplayMonth: Date,
|
||||
onPrevMonth: () => void,
|
||||
onNextMonth: () => void
|
||||
): HTMLElement {
|
||||
const header = document.createElement("div");
|
||||
header.className = "calendar-header";
|
||||
|
||||
// --- Previous Month Button ---
|
||||
const prevBtn = header.createEl("button", { text: "←", cls: "clickable-icon" });
|
||||
prevBtn.setAttribute("aria-label", "Previous month");
|
||||
prevBtn.onclick = onPrevMonth; // Assign the provided handler
|
||||
|
||||
// --- Month/Year Title ---
|
||||
const title = header.createEl("span", {
|
||||
cls: "calendar-title",
|
||||
text: currentDisplayMonth.toLocaleString("default", { // Use user's default locale
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
timeZone: 'UTC' // Ensure month/year is based on UTC date
|
||||
}),
|
||||
});
|
||||
|
||||
// --- Next Month Button ---
|
||||
const nextBtn = header.createEl("button", { text: "→", cls: "clickable-icon" });
|
||||
nextBtn.setAttribute("aria-label", "Next month");
|
||||
nextBtn.onclick = onNextMonth; // Assign the provided handler
|
||||
|
||||
return header;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Builds the row displaying weekday abbreviations (Mo, Tu, etc.).
|
||||
* @param includeWeekNumber - If true, adds a "W#" header cell at the beginning.
|
||||
* @returns The HTMLElement representing the weekday header row.
|
||||
*/
|
||||
export function buildWeekdayHeaders(includeWeekNumber: boolean = false): HTMLElement {
|
||||
const weekdaysContainer = document.createElement("div");
|
||||
weekdaysContainer.className = "calendar-weekdays";
|
||||
|
||||
//? Header for Week# column (optional)
|
||||
if (includeWeekNumber) {
|
||||
weekdaysContainer.createDiv({ cls: "week-number-header", text: "W#" });
|
||||
}
|
||||
|
||||
//? Standard Monday-first weekdays
|
||||
const weekdays = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
||||
weekdays.forEach(day => weekdaysContainer.createDiv({ cls: "calendar-weekday", text: day }));
|
||||
|
||||
return weekdaysContainer;
|
||||
}
|
||||
43
src/codeblocks/dateHeader/index.ts
Normal file
43
src/codeblocks/dateHeader/index.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { MarkdownRenderChild, App } from 'obsidian';
|
||||
|
||||
import { DateNavigator } from './components/dateNavigator/DateNavigator';
|
||||
import { pluginState } from '../../pluginState';
|
||||
|
||||
/**
|
||||
* An Obsidian Render Child responsible for rendering and managing the lifecycle
|
||||
* of a DateNavigator instance within a Markdown code block.
|
||||
*/
|
||||
export class DateNavigatorRenderer extends MarkdownRenderChild {
|
||||
|
||||
private navigator: DateNavigator | null = null;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
private appInstance: App
|
||||
) {
|
||||
super(containerEl);
|
||||
}
|
||||
|
||||
/** Called by Obsidian when the component should be loaded/rendered. */
|
||||
onload() {
|
||||
this.containerEl.empty();
|
||||
this.containerEl.addClass('date-navigator-container-wrapper');
|
||||
|
||||
try {
|
||||
this.navigator = new DateNavigator({
|
||||
app: this.appInstance,
|
||||
pluginState: pluginState,
|
||||
containerEl: this.containerEl
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[DateNavigatorRenderer] Failed to create DateNavigator:", error);
|
||||
this.containerEl.setText('Error loading date navigator component.');
|
||||
}
|
||||
}
|
||||
|
||||
/** Called by Obsidian when the component should be unloaded/cleaned up. */
|
||||
onunload() {
|
||||
this.navigator?.destroy();
|
||||
this.navigator = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
import { Modal, App, Notice, MarkdownView } from "obsidian";
|
||||
import { pluginState } from "../../pluginState"; // Assuming this exists and works
|
||||
|
||||
function getISOWeekNumber(date: Date): number {
|
||||
const temp = new Date(date.getTime());
|
||||
temp.setHours(0, 0, 0, 0);
|
||||
// Thursday in current week decides the year.
|
||||
temp.setDate(temp.getDate() + 3 - ((temp.getDay() + 6) % 7));
|
||||
const week1 = new Date(temp.getFullYear(), 0, 4);
|
||||
return (
|
||||
1 +
|
||||
Math.round(
|
||||
((temp.getTime() - week1.getTime()) / 86400000 - ((week1.getDay() + 6) % 7)) /
|
||||
7
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export class DatePickerModal extends Modal {
|
||||
private selectedDate: Date;
|
||||
private currentMonth: Date;
|
||||
private today: Date = new Date();
|
||||
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
// Ensure selectedDate is always a Date object
|
||||
const initialDate = pluginState.selectedDate ? new Date(pluginState.selectedDate) : new Date();
|
||||
// Validate if the parsed date is valid, otherwise default to today
|
||||
this.selectedDate = isNaN(initialDate.getTime()) ? new Date() : initialDate;
|
||||
// Set time to 00:00:00 to compare dates easily
|
||||
this.selectedDate.setHours(0, 0, 0, 0);
|
||||
this.today.setHours(0, 0, 0, 0);
|
||||
|
||||
this.currentMonth = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), 1);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
// Add a class to the modal content for easier styling
|
||||
this.contentEl.addClass("custom-datepicker-modal-content");
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty(); // Clear previous content
|
||||
|
||||
// --- Header ---
|
||||
const header = contentEl.createEl("div", { cls: "calendar-header" });
|
||||
const prevBtn = header.createEl("button", { text: "←", cls: "clickable-icon" });
|
||||
prevBtn.setAttribute("aria-label", "Previous month");
|
||||
prevBtn.onclick = () => {
|
||||
this.currentMonth.setMonth(this.currentMonth.getMonth() - 1);
|
||||
this.render();
|
||||
};
|
||||
|
||||
const title = header.createEl("span", {
|
||||
cls: "calendar-title",
|
||||
text: this.currentMonth.toLocaleString("default", { month: "long", year: "numeric" }),
|
||||
});
|
||||
|
||||
const nextBtn = header.createEl("button", { text: "→", cls: "clickable-icon" });
|
||||
nextBtn.setAttribute("aria-label", "Next month");
|
||||
nextBtn.onclick = () => {
|
||||
this.currentMonth.setMonth(this.currentMonth.getMonth() + 1);
|
||||
this.render();
|
||||
};
|
||||
|
||||
// --- Weekday Headers ---
|
||||
const weekdaysContainer = contentEl.createDiv({ cls: "calendar-weekdays" });
|
||||
const weekdays = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
||||
weekdaysContainer.createDiv({ cls: "week-number-header", text: "W#" }); // Header for Week#
|
||||
weekdays.forEach(day => weekdaysContainer.createDiv({ cls: "calendar-weekday", text: day }));
|
||||
|
||||
// --- Calendar Grid ---
|
||||
// buildCalendar now returns the grid itself
|
||||
const calendarGrid = this.buildCalendar();
|
||||
contentEl.appendChild(calendarGrid);
|
||||
|
||||
// --- Footer ---
|
||||
const footer = contentEl.createDiv({ cls: "calendar-footer" });
|
||||
// Display currently selected date
|
||||
const selectedDateDisplay = footer.createEl("span", { cls: "selected-date-display" });
|
||||
this.updateSelectedDateDisplay(selectedDateDisplay); // Initial display
|
||||
|
||||
const confirmBtn = footer.createEl("button", { text: "Select", cls: "mod-cta" }); // Use Obsidian's primary button style
|
||||
confirmBtn.onclick = () => {
|
||||
const iso = `${this.selectedDate.getFullYear()}-${String(this.selectedDate.getMonth() + 1).padStart(2, '0')}-${String(this.selectedDate.getDate()).padStart(2, '0')}`;
|
||||
pluginState.selectedDate = iso; // Update global state
|
||||
new Notice(`Date set to ${iso}`);
|
||||
// Optional: Force re-render if your plugin uses the date reactively in preview
|
||||
this.app.workspace.getActiveViewOfType(MarkdownView)?.previewMode?.rerender(true);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
private buildCalendar(): HTMLElement {
|
||||
const grid = document.createElement("div");
|
||||
grid.className = "calendar-grid"; // This will be the actual grid container
|
||||
|
||||
const year = this.currentMonth.getFullYear();
|
||||
const month = this.currentMonth.getMonth();
|
||||
const firstDayOfMonth = new Date(year, month, 1);
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
|
||||
// Calculate offset for Monday start (0=Mon, 6=Sun)
|
||||
const startOffset = (firstDayOfMonth.getDay() + 6) % 7;
|
||||
|
||||
// --- Add Week Numbers and Empty Cells before the 1st ---
|
||||
let day = 1 - startOffset; // Start day counter considering offset
|
||||
let currentWeekNumber = -1;
|
||||
|
||||
while (day <= daysInMonth) {
|
||||
// Calculate and add week number cell for the current row
|
||||
const dateForWeekNum = new Date(year, month, day > 0 ? day : 1); // Use day 1 if current day is placeholder
|
||||
const weekNumber = getISOWeekNumber(dateForWeekNum);
|
||||
// Only add week number if it changed (start of a new row essentially)
|
||||
if (weekNumber !== currentWeekNumber) {
|
||||
const weekEl = document.createElement("div");
|
||||
weekEl.className = "week-number";
|
||||
weekEl.textContent = `${String(weekNumber).padStart(2, "0")}`;
|
||||
grid.appendChild(weekEl);
|
||||
currentWeekNumber = weekNumber;
|
||||
} else if (grid.children.length % 8 === 0) {
|
||||
// If week number didn't change but we are at start of grid row, add it
|
||||
// This handles month starts that continue week number from previous month correctly
|
||||
const weekEl = document.createElement("div");
|
||||
weekEl.className = "week-number";
|
||||
weekEl.textContent = `${String(weekNumber).padStart(2, "0")}`;
|
||||
grid.appendChild(weekEl);
|
||||
}
|
||||
|
||||
|
||||
// Add the 7 day cells for the week
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const cell = document.createElement("div"); // Use a div as container for button
|
||||
cell.className = "calendar-day-cell";
|
||||
|
||||
if (day < 1 || day > daysInMonth) {
|
||||
// Empty cell (before start or after end of month)
|
||||
cell.classList.add("empty");
|
||||
} else {
|
||||
const currentDate = new Date(year, month, day);
|
||||
currentDate.setHours(0, 0, 0, 0); // Normalize time for comparison
|
||||
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = String(day);
|
||||
btn.className = "calendar-day";
|
||||
btn.setAttribute("aria-label", currentDate.toDateString());
|
||||
|
||||
// Check if this is the currently selected date
|
||||
if (currentDate.getTime() === this.selectedDate.getTime()) {
|
||||
btn.classList.add("selected");
|
||||
btn.setAttribute("aria-selected", "true");
|
||||
} else {
|
||||
btn.setAttribute("aria-selected", "false");
|
||||
}
|
||||
|
||||
// Check if this is today's date
|
||||
if (currentDate.getTime() === this.today.getTime()) {
|
||||
btn.classList.add("today");
|
||||
}
|
||||
|
||||
btn.onclick = () => {
|
||||
// Update selected date state
|
||||
this.selectedDate = currentDate;
|
||||
|
||||
const previouslySelected = grid.querySelector(".calendar-day.selected");
|
||||
if (previouslySelected) {
|
||||
previouslySelected.classList.remove("selected");
|
||||
previouslySelected.setAttribute("aria-selected", "false");
|
||||
}
|
||||
btn.classList.add("selected");
|
||||
btn.setAttribute("aria-selected", "true");
|
||||
|
||||
this.updateSelectedDateDisplay();
|
||||
};
|
||||
cell.appendChild(btn);
|
||||
}
|
||||
grid.appendChild(cell);
|
||||
day++;
|
||||
}
|
||||
}
|
||||
return grid;
|
||||
}
|
||||
|
||||
private updateSelectedDateDisplay(element?: HTMLElement) {
|
||||
const displayEl = element ?? this.contentEl.querySelector(".selected-date-display");
|
||||
if (displayEl) {
|
||||
displayEl.textContent = `Selected: ${this.selectedDate.toLocaleDateString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onClose() {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { App } from "obsidian";
|
||||
import { DatePickerModal } from "./DatePickerModal";
|
||||
|
||||
export function renderDatePicker(container: HTMLElement, app: App) {
|
||||
const wrapper = container.createDiv({ cls: "datepicker" });
|
||||
|
||||
const button = wrapper.createEl("button", { text: "Select Date" });
|
||||
button.onclick = () => {
|
||||
new DatePickerModal(app).open();
|
||||
};
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { processSqlBlock } from "./processSqlBlock";
|
||||
import { processSqlChartBlock } from "./processSqlChartBlock";
|
||||
import { renderDatePicker } from "./datePicker";
|
||||
import { DateNavigatorRenderer } from "./dateHeader";
|
||||
|
||||
export { processSqlBlock, processSqlChartBlock, renderDatePicker };
|
||||
export { processSqlBlock, processSqlChartBlock, DateNavigatorRenderer };
|
||||
|
|
@ -1,17 +1,26 @@
|
|||
export const DATE_CHANGED_EVENT_NAME = 'plugin-date-changed';
|
||||
|
||||
export class PluginState {
|
||||
private _selectedDate: string;
|
||||
private _selectedDate: string;
|
||||
|
||||
constructor() {
|
||||
this._selectedDate = new Date().toISOString().split("T")[0];
|
||||
}
|
||||
constructor() {
|
||||
this._selectedDate = new Date().toISOString().split("T")[0];
|
||||
}
|
||||
|
||||
get selectedDate(): string {
|
||||
return this._selectedDate;
|
||||
}
|
||||
get selectedDate(): string {
|
||||
return this._selectedDate;
|
||||
}
|
||||
|
||||
set selectedDate(newDate: string) {
|
||||
this._selectedDate = newDate;
|
||||
}
|
||||
set selectedDate(newIsoDate: string) {
|
||||
if (this._selectedDate !== newIsoDate) {
|
||||
this._selectedDate = newIsoDate;
|
||||
document.dispatchEvent(new CustomEvent(DATE_CHANGED_EVENT_NAME, {
|
||||
detail: { newIsoDate: this._selectedDate } // Pass the new date
|
||||
}));
|
||||
} else {
|
||||
//& console.log(`[PluginState] Date unchanged: ${newIsoDate}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const pluginState = new PluginState();
|
||||
export const pluginState = new PluginState();
|
||||
59
src/styles/dateNavigationInject.ts
Normal file
59
src/styles/dateNavigationInject.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
//? Define the unique class used by the DateNavigatorRenderer container
|
||||
//? Define a unique ID for the style tag specific to the navigator
|
||||
const STYLE_ID = "sqlitedb-date-navigator-styles";
|
||||
const NAVIGATOR_CONTAINER_WRAPPER_CLASS = "date-navigator-container-wrapper";
|
||||
|
||||
/** Returns the CSS string specifically for the Date Navigator component. */
|
||||
function getDateNavigatorStyles(): string {
|
||||
return `
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} {
|
||||
padding-bottom: var(--size-4-2);
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} .date-navigator-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column; /* Stack nav row and button vertically */
|
||||
align-items: center;
|
||||
gap: var(--size-4-2); /* Space between nav row and button */
|
||||
}
|
||||
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} .date-nav-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--size-4-3);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.${NAVIGATOR_CONTAINER_WRAPPER_CLASS} h1.date-navigator-display {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
flex-grow: 1;
|
||||
font-size: var(--font-heading-1);
|
||||
color: var(--text-error);
|
||||
white-space: nowrap;
|
||||
min-width: 15ch;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/** Injects CSS for the Date Navigator component into the document head if not already present. */
|
||||
export function injectDateNavigatorStyles(): void {
|
||||
if (document.getElementById(STYLE_ID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = STYLE_ID;
|
||||
styleEl.textContent = getDateNavigatorStyles();
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
/** Removes the injected Date Navigator styles from the document head. */
|
||||
export function removeDateNavigatorStyles(): void {
|
||||
const styleEl = document.getElementById(STYLE_ID);
|
||||
if (styleEl) {
|
||||
styleEl.remove();
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import {
|
|||
clearComponentErrorState,
|
||||
HabitCounterUIElements
|
||||
} from "./dom/uiUpdaters";
|
||||
import { DATE_CHANGED_EVENT_NAME, pluginState } from "../../pluginState";
|
||||
|
||||
//? Main Web Component class, acts as an orchestrator.
|
||||
export class HabitCounter extends HTMLElement {
|
||||
|
|
@ -36,6 +37,8 @@ export class HabitCounter extends HTMLElement {
|
|||
wrapper: null, valueDisplay: null, labelElement: null, minusButton: null, plusButton: null
|
||||
};
|
||||
|
||||
private _isListeningForDateChanges: boolean = false; // Prevent multiple listeners
|
||||
|
||||
// --- Lifecycle ---
|
||||
connectedCallback() {
|
||||
if (this._isInitialized) return;
|
||||
|
|
@ -48,9 +51,15 @@ export class HabitCounter extends HTMLElement {
|
|||
|
||||
this.uiElements = buildHabitCounterDOM(this.attachShadow({ mode: "open" }), handlers); //~ Build DOM via helper
|
||||
applyHabitCounterStyles(this.shadowRoot!); //~ Apply styles via helper
|
||||
this._setupDateChangeListener(); //^ Call setup listener
|
||||
this._isInitialized = true;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
document.removeEventListener(DATE_CHANGED_EVENT_NAME, this._handleGlobalDateChange);
|
||||
this._isListeningForDateChanges = false;
|
||||
}
|
||||
|
||||
// --- Public Methods ---
|
||||
public setDbService(service: DBService): void {
|
||||
if (!service) {
|
||||
|
|
@ -98,6 +107,32 @@ export class HabitCounter extends HTMLElement {
|
|||
});
|
||||
}
|
||||
|
||||
private _setupDateChangeListener(): void {
|
||||
//? Only add listener if component uses dynamic date and not already listening
|
||||
if (this.getAttribute("date") === "@date" && !this._isListeningForDateChanges) {
|
||||
document.addEventListener(DATE_CHANGED_EVENT_NAME, this._handleGlobalDateChange);
|
||||
this._isListeningForDateChanges = true;
|
||||
} else {
|
||||
//& console.log(`[HabitCounter Component ${this.habitKey}] Not adding listener (date=${this.getAttribute("date")}, listening=${this._isListeningForDateChanges})`);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleGlobalDateChange = (event: Event): void => {
|
||||
//? Type guard for CustomEvent
|
||||
if (!(event instanceof CustomEvent)) return;
|
||||
|
||||
const newIsoDate = event.detail?.newIsoDate;
|
||||
|
||||
//? Double-check this component should react (uses @date)
|
||||
if (this.initialDate === "@date") {
|
||||
|
||||
//? Reload data using the same helper
|
||||
loadHabitData(this).catch(err => {
|
||||
console.error(`[HabitCounter Component ${this.habitKey}] Error reloading data after date change:`, err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//? Public method called by click handlers via instance reference
|
||||
public async _updateData(delta: number): Promise<void> {
|
||||
await updateHabitData(this, delta); //~ Update data via helper
|
||||
|
|
|
|||
Loading…
Reference in a new issue