mirror of
https://github.com/stfrigerio/sqliteDB.git
synced 2026-07-22 05:38:02 +00:00
date selector modal and date parsing in blocks
This commit is contained in:
parent
a3be482a27
commit
f97f55ff67
8 changed files with 414 additions and 10 deletions
21
main.ts
21
main.ts
|
|
@ -3,7 +3,6 @@ import {
|
|||
Plugin,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
Notice,
|
||||
FileSystemAdapter,
|
||||
Editor,
|
||||
MarkdownView,
|
||||
|
|
@ -12,22 +11,23 @@ import {
|
|||
|
||||
import { DBService } from "./src/dbService";
|
||||
import { inspectTableStructure, convertEntriesInNotes } from "./src/commands";
|
||||
import { processSqlBlock, processSqlChartBlock } from "./src/codeblocks";
|
||||
import { processSqlBlock, processSqlChartBlock, renderDatePicker } from "./src/codeblocks";
|
||||
import { pickTableName } from "./src/helpers";
|
||||
import { SQLiteDBSettings, DEFAULT_SETTINGS } from "./src/types";
|
||||
import { injectDatePickerStyles } from "src/styles/datePickerInject";
|
||||
|
||||
export default class SQLiteDBPlugin extends Plugin {
|
||||
settings: SQLiteDBSettings;
|
||||
private dbService: DBService;
|
||||
|
||||
async onload() {
|
||||
console.log("Loading SQLiteDBPlugin...");
|
||||
// init
|
||||
await this.loadSettings();
|
||||
|
||||
this.dbService = new DBService(this.app);
|
||||
|
||||
await this.openDatabase();
|
||||
|
||||
injectDatePickerStyles();
|
||||
|
||||
this.addCommand({
|
||||
id: "inspect-table-structure",
|
||||
name: "Inspect table structure",
|
||||
|
|
@ -42,7 +42,7 @@ export default class SQLiteDBPlugin extends Plugin {
|
|||
id: "dump-table-to-notes",
|
||||
name: "Dump table to notes",
|
||||
callback: async () => {
|
||||
await this.openDatabase(); // ensure DB is loaded
|
||||
await this.openDatabase();
|
||||
|
||||
// 1) pick a table
|
||||
const chosenTable = await pickTableName(this.dbService, this.app);
|
||||
|
|
@ -56,7 +56,7 @@ export default class SQLiteDBPlugin extends Plugin {
|
|||
});
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor(
|
||||
"sql", // <-- the name of your code block (```sql)
|
||||
"sql",
|
||||
async (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
await processSqlBlock(this.dbService, source, el);
|
||||
}
|
||||
|
|
@ -69,6 +69,13 @@ export default class SQLiteDBPlugin extends Plugin {
|
|||
}
|
||||
);
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor(
|
||||
"date-picker",
|
||||
async (source: string, el: HTMLElement) => {
|
||||
renderDatePicker(el, this.app);
|
||||
}
|
||||
);
|
||||
|
||||
this.addSettingTab(new SQLiteDBSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
|
|
|
|||
197
src/codeblocks/datePicker/DatePickerModal.ts
Normal file
197
src/codeblocks/datePicker/DatePickerModal.ts
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
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.toISOString().split("T")[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();
|
||||
}
|
||||
}
|
||||
11
src/codeblocks/datePicker/index.ts
Normal file
11
src/codeblocks/datePicker/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
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,4 +1,5 @@
|
|||
import { processSqlBlock } from "./processSqlBlock";
|
||||
import { processSqlChartBlock } from "./processSqlChartBlock";
|
||||
import { renderDatePicker } from "./datePicker";
|
||||
|
||||
export { processSqlBlock, processSqlChartBlock };
|
||||
export { processSqlBlock, processSqlChartBlock, renderDatePicker };
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { Notice } from "obsidian";
|
||||
import { DBService } from "../../dbService";
|
||||
import { parseSqlParams, validateTable, buildSqlQuery, renderResults } from "./helpers";
|
||||
import { pluginState } from "../../pluginState";
|
||||
|
||||
export async function processSqlBlock(dbService: DBService, source: string, el: HTMLElement) {
|
||||
const db = dbService.getDB();
|
||||
|
|
@ -10,7 +11,10 @@ export async function processSqlBlock(dbService: DBService, source: string, el:
|
|||
return;
|
||||
}
|
||||
|
||||
const params = parseSqlParams(source);
|
||||
// replace @date with the selected date
|
||||
const injectedSource = source.replace(/@date/g, pluginState.selectedDate);
|
||||
const params = parseSqlParams(injectedSource);
|
||||
|
||||
if (!params) {
|
||||
el.createEl("p", { text: "Missing required parameter: table" });
|
||||
el.createEl("p", { text: "Example usage:" });
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Notice } from "obsidian";
|
|||
import { DBService } from "../../dbService";
|
||||
import { parseChartParams, validateColumns, buildSqlQuery, processChartData } from "./helpers";
|
||||
import { ChartType, createChart } from "./charts";
|
||||
import { pluginState } from "../../pluginState";
|
||||
|
||||
export async function processSqlChartBlock(dbService: DBService, source: string, el: HTMLElement) {
|
||||
const db = dbService.getDB();
|
||||
|
|
@ -11,7 +12,10 @@ export async function processSqlChartBlock(dbService: DBService, source: string,
|
|||
return;
|
||||
}
|
||||
|
||||
const config = parseChartParams(source);
|
||||
// replace @date with the selected date
|
||||
const injectedSource = source.replace(/@date/g, pluginState.selectedDate);
|
||||
const config = parseChartParams(injectedSource);
|
||||
|
||||
if (!config) {
|
||||
// @ts-ignore
|
||||
const message = config?.chartType === 'pie'
|
||||
|
|
|
|||
17
src/pluginState.ts
Normal file
17
src/pluginState.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
export class PluginState {
|
||||
private _selectedDate: string;
|
||||
|
||||
constructor() {
|
||||
this._selectedDate = new Date().toISOString().split("T")[0];
|
||||
}
|
||||
|
||||
get selectedDate(): string {
|
||||
return this._selectedDate;
|
||||
}
|
||||
|
||||
set selectedDate(newDate: string) {
|
||||
this._selectedDate = newDate;
|
||||
}
|
||||
}
|
||||
|
||||
export const pluginState = new PluginState();
|
||||
163
src/styles/datePickerInject.ts
Normal file
163
src/styles/datePickerInject.ts
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
export function injectDatePickerStyles() {
|
||||
const styleId = "custom-datepicker-styles";
|
||||
// Avoid injecting multiple times
|
||||
if (document.getElementById(styleId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.id = styleId;
|
||||
style.textContent = `
|
||||
/* Style the modal content area */
|
||||
.custom-datepicker-modal-content {
|
||||
padding: var(--size-4-4) var(--size-4-6); /* Consistent padding */
|
||||
}
|
||||
|
||||
/* Header: Title and Navigation Buttons */
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--size-4-3); /* Space below header */
|
||||
}
|
||||
.calendar-title {
|
||||
font-weight: var(--font-bold);
|
||||
font-size: var(--font-ui-large);
|
||||
}
|
||||
.calendar-header button.clickable-icon {
|
||||
/* Use Obsidian's icon button style */
|
||||
padding: var(--size-4-1) var(--size-4-2);
|
||||
font-size: var(--font-ui-large); /* Make arrows bigger */
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Weekday Headers Row */
|
||||
.calendar-weekdays {
|
||||
display: grid;
|
||||
grid-template-columns: 36px repeat(7, 1fr); /* Week# + 7 days */
|
||||
gap: var(--size-4-1);
|
||||
padding-bottom: var(--size-4-1);
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
margin-bottom: var(--size-4-2);
|
||||
}
|
||||
.calendar-weekday,
|
||||
.week-number-header {
|
||||
text-align: center;
|
||||
font-weight: var(--font-semibold);
|
||||
font-size: var(--font-ui-small);
|
||||
color: var(--text-muted);
|
||||
line-height: 1; /* Ensure consistent height */
|
||||
}
|
||||
.week-number-header {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Calendar Grid: Week Numbers and Day Cells */
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 36px repeat(7, 1fr); /* Week# + 7 days */
|
||||
gap: var(--size-4-1); /* Gap between cells */
|
||||
align-items: center; /* Vertically center content in grid rows */
|
||||
}
|
||||
|
||||
/* Individual cell container (holds button or is empty) */
|
||||
.calendar-day-cell {
|
||||
display: flex; /* Center button inside */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
aspect-ratio: 1 / 1; /* Make cells squareish */
|
||||
min-height: 30px; /* Ensure minimum height */
|
||||
}
|
||||
.calendar-day-cell.empty {
|
||||
/* Style for empty cells (before/after month) */
|
||||
/* background-color: var(--background-secondary); */ /* Subtle background */
|
||||
/* opacity: 0.5; */
|
||||
/* Or just leave them blank */
|
||||
}
|
||||
|
||||
|
||||
/* Week Number Cells in the main grid */
|
||||
.week-number {
|
||||
text-align: center;
|
||||
font-size: var(--font-ui-smaller);
|
||||
color: var(--text-faint);
|
||||
font-style: italic;
|
||||
align-self: center; /* Align vertically in grid row */
|
||||
padding: 0 var(--size-4-1); /* Add some horizontal padding */
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
|
||||
/* Day Buttons */
|
||||
.calendar-day {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%; /* Fill cell width */
|
||||
height: 100%; /* Fill cell height */
|
||||
padding: var(--size-4-1);
|
||||
border: 1px solid transparent; /* Reserve space for border */
|
||||
border-radius: var(--radius-s);
|
||||
background-color: var(--background-secondary);
|
||||
color: var(--text-normal);
|
||||
font-size: var(--font-ui-small);
|
||||
cursor: pointer;
|
||||
transition: all 0.1s ease-in-out;
|
||||
line-height: 1; /* Prevent text pushing button size */
|
||||
}
|
||||
|
||||
.calendar-day:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
color: var(--text-normal); /* Keep text readable on hover */
|
||||
border-color: var(--background-modifier-border-hover);
|
||||
}
|
||||
|
||||
/* Today's Date Indicator */
|
||||
.calendar-day.today {
|
||||
border-color: var(--text-accent); /* Use accent color for border */
|
||||
/* Optional: Add a subtle background or different font weight */
|
||||
/* background-color: var(--background-primary-alt); */
|
||||
/* font-weight: var(--font-bold); */
|
||||
}
|
||||
|
||||
/* Selected Date Styling */
|
||||
.calendar-day.selected {
|
||||
background-color: var(--interactive-accent); /* Use theme's accent color */
|
||||
color: var(--text-on-accent);
|
||||
font-weight: var(--font-bold);
|
||||
border-color: var(--interactive-accent-hover); /* Slightly darker border */
|
||||
}
|
||||
.calendar-day.selected:hover {
|
||||
background-color: var(--interactive-accent-hover); /* Darken on hover */
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
/* Today and Selected */
|
||||
.calendar-day.today.selected {
|
||||
/* Optional: Make combo distinct if needed, e.g., thicker border */
|
||||
/* border-width: 2px; */
|
||||
}
|
||||
|
||||
|
||||
/* Footer Area */
|
||||
.calendar-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: var(--size-4-3); /* Space above footer */
|
||||
padding-top: var(--size-4-2);
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
.selected-date-display {
|
||||
font-size: var(--font-ui-small);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.calendar-footer button {
|
||||
/* Let .mod-cta handle primary button style */
|
||||
/* Add margin if needed */
|
||||
/* margin-left: var(--size-4-2); */
|
||||
}
|
||||
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
Loading…
Reference in a new issue