const calendarContainer = this.container;
const today = moment().startOf('day');
let displayedMonth = moment(localStorage.getItem('selectedMonth') || today).startOf('month');
let activeTooltip = null;
let tooltipTimeout = null;
// Set locale to English
moment.locale('en');
const clearTooltips = () => document.querySelectorAll('.calendar-tooltip').forEach(tooltip => tooltip.remove());
const updateTaskStatus = async (task) => {
const tasksApi = app.plugins.plugins['obsidian-tasks-plugin'].apiV1;
if (!tasksApi) {
console.error("Tasks API is not available");
return;
}
const file = app.vault.getAbstractFileByPath(task.path);
if (!(file instanceof obsidian.TFile)) {
console.error("File not found.");
return;
}
const content = await app.vault.read(file);
const lines = content.split('\n');
const taskLineIndex = lines.findIndex(line => line.includes(task.text));
if (taskLineIndex === -1) {
console.log("Task not found in the file.");
return;
}
const updatedTaskLine = tasksApi.executeToggleTaskDoneCommand(lines[taskLineIndex], file.path);
if (updatedTaskLine === lines[taskLineIndex]) {
console.log("Task did not change.");
return;
}
lines[taskLineIndex] = updatedTaskLine;
await app.vault.modify(file, lines.join('\n'));
console.log("Task updated successfully!");
task.completed = !task.completed;
};
const openTaskInNote = (task) => {
const file = app.vault.getAbstractFileByPath(task.path);
if (!(file instanceof obsidian.TFile)) return;
app.workspace.getLeaf(false).openFile(file).then(() => {
const view = app.workspace.getActiveViewOfType(obsidian.MarkdownView);
if (!view) return;
const editor = view.editor;
const content = editor.getValue();
const taskLine = content.split('\n').findIndex(line => line.includes(task.text));
if (taskLine === -1) return;
editor.setCursor({ line: taskLine, ch: 0 });
editor.scrollIntoView({ from: { line: taskLine, ch: 0 }, to: { line: taskLine, ch: 0 } }, true);
});
};
const createButton = (text, onClick) => {
const button = document.createElement('button');
button.textContent = text;
button.onclick = onClick;
return button;
};
const renderCalendar = () => {
calendarContainer.innerHTML = '';
const monthStart = displayedMonth.clone().startOf('month');
const monthEnd = displayedMonth.clone().endOf('month');
const firstDayToShow = monthStart.clone().startOf('week');
const lastDayToShow = monthEnd.clone().endOf('week');
const days = [];
let day = firstDayToShow.clone();
while (day.isBefore(lastDayToShow.clone().add(1, 'day'), 'day')) {
days.push(day.clone());
day.add(1, 'day');
}
const navContainer = document.createElement('div');
navContainer.style.cssText = 'display: flex; gap: 10px; margin-bottom: 10px; justify-content: center;';
const updateDisplayedMonth = (modifier) => {
displayedMonth[modifier](1, 'month');
localStorage.setItem('selectedMonth', displayedMonth.toISOString());
renderCalendar();
};
navContainer.appendChild(createButton('⏪', () => updateDisplayedMonth('subtract')));
navContainer.appendChild(createButton('◀', () => updateDisplayedMonth('subtract')));
navContainer.appendChild(createButton('🏠', () => {
displayedMonth = moment(today).startOf('month');
localStorage.setItem('selectedMonth', displayedMonth.toISOString());
renderCalendar();
}));
navContainer.appendChild(createButton('▶', () => updateDisplayedMonth('add')));
navContainer.appendChild(createButton('⏩', () => updateDisplayedMonth('add')));
calendarContainer.appendChild(navContainer);
// Ensure month name is displayed in English
const title = document.createElement('h3');
title.textContent = displayedMonth.format('MMMM YYYY');
title.style.cssText = 'text-align: center; margin-top: 10px;';
calendarContainer.appendChild(title);
const calendarGrid = document.createElement('div');
calendarGrid.style.cssText = 'display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; margin: 0 auto; max-width: 350px; width: 100%;';
// English day names
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].forEach(dayOfWeek => {
const dayHeader = document.createElement('div');
dayHeader.textContent = dayOfWeek;
dayHeader.style.cssText = 'text-align: center; font-weight: bold;';
calendarGrid.appendChild(dayHeader);
});
const tasksCache = new Map();
const getTasksForDay = (date) => {
const dateString = date.format('YYYY-MM-DD');
if (!tasksCache.has(dateString)) {
tasksCache.set(dateString, dv.pages('#task')
.flatMap(p => p.file.tasks.map(task => ({ ...task, path: p.file.path })))
.filter(task => {
const taskDate = task.text.match(/📅 (\d{4}-\d{2}-\d{2})/);
return taskDate && moment(taskDate[1], 'YYYY-MM-DD').isSame(date, 'day');
}));
}
return tasksCache.get(dateString);
};
days.forEach(date => {
const dayCell = document.createElement('div');
dayCell.style.cssText = 'width: 100%; height: 50px; border-radius: 10%; display: flex; justify-content: center; align-items: center; position: relative; cursor: pointer;';
dayCell.style.backgroundColor = date.month() === displayedMonth.month() ? '#fcf7bb' : '#baa791';
dayCell.style.color = 'black';
if (date.isSame(today, 'day')) {
dayCell.style.cssText += 'background-color: gold; font-weight: bold;';
}
const dayNumber = document.createElement('span');
dayNumber.textContent = date.date();
dayNumber.style.fontSize = '16px';
dayCell.appendChild(dayNumber);
const tasksForDay = getTasksForDay(date);
if (tasksForDay.length > 0) {
const incompleteTasks = tasksForDay.filter(task => !task.completed);
const completedTasks = tasksForDay.filter(task => task.completed);
if (incompleteTasks.length > 0) {
const redCircle = document.createElement('div');
redCircle.style.cssText = 'position: absolute; bottom: 2px; right: 2px; width: 15px; height: 15px; border-radius: 50%; background-color: red; display: flex; justify-content: center; align-items: center; font-size: 12px; color: white;';
redCircle.textContent = incompleteTasks.length;
dayCell.appendChild(redCircle);
}
if (completedTasks.length > 0) {
const greenCircle = document.createElement('div');
greenCircle.style.cssText = 'position: absolute; bottom: 2px; left: 2px; width: 15px; height: 15px; border-radius: 50%; background-color: green; display: flex; justify-content: center; align-items: center; font-size: 12px; color: white;';
greenCircle.textContent = completedTasks.length;
dayCell.appendChild(greenCircle);
}
dayCell.onmouseover = () => {
if (tooltipTimeout) clearTimeout(tooltipTimeout);
tooltipTimeout = setTimeout(() => {
clearTooltips();
const tooltip = document.createElement('div');
tooltip.classList.add('calendar-tooltip');
tooltip.style.cssText = 'position: absolute; background-color: #333; color: #fff; border: 1px solid #444; padding: 10px; box-shadow: 0px 0px 10px rgba(0,0,0,0.5); z-index: 10; min-width: 250px; max-width: 400px; border-radius: 5px; transition: opacity 0.3s ease, visibility 0.3s ease; opacity: 0; visibility: hidden; white-space: pre-wrap; word-wrap: break-word;';
const rect = dayCell.getBoundingClientRect();
const windowWidth = window.innerWidth;
const tooltipWidth = 400;
const offsetX = 10;
let leftPosition = Math.min(rect.left, windowWidth - tooltipWidth - offsetX);
leftPosition = Math.max(leftPosition, 0);
tooltip.style.top = `${rect.bottom + window.scrollY}px`;
tooltip.style.left = `${leftPosition}px`;
document.body.appendChild(tooltip);
requestAnimationFrame(() => {
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
});
tasksForDay.forEach(task => {
const taskDiv = document.createElement('div');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = task.completed;
checkbox.onchange = async () => {
await updateTaskStatus(task);
renderCalendar();
};
const taskText = document.createElement('span');
taskText.textContent = task.text;
taskText.style.cursor = 'pointer';
taskText.onclick = () => openTaskInNote(task);
taskDiv.appendChild(checkbox);
taskDiv.appendChild(taskText);
taskDiv.style.paddingBottom = '5px';
tooltip.appendChild(taskDiv);
});
tooltip.onmouseenter = () => {
if (tooltipTimeout) clearTimeout(tooltipTimeout);
tooltip.style.opacity = '1';
tooltip.style.visibility = 'visible';
};
tooltip.onmouseleave = () => {
tooltipTimeout = setTimeout(() => {
if (activeTooltip) {
activeTooltip.style.opacity = '0';
activeTooltip.style.visibility = 'hidden';
tooltipTimeout = setTimeout(() => {
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
}, 300);
}
}, 250);
};
activeTooltip = tooltip;
}, 300);
};
dayCell.onmouseleave = (e) => {
if (tooltipTimeout) clearTimeout(tooltipTimeout);
if (activeTooltip) {
const tooltipRect = activeTooltip.getBoundingClientRect();
const mouseX = e.clientX;
const mouseY = e.clientY;
const isMouseOverTooltip = mouseX >= tooltipRect.left && mouseX <= tooltipRect.right && mouseY >= tooltipRect.top && mouseY <= tooltipRect.bottom;
if (!isMouseOverTooltip) {
tooltipTimeout = setTimeout(() => {
if (activeTooltip) {
activeTooltip.style.opacity = '0';
activeTooltip.style.visibility = 'hidden';
tooltipTimeout = setTimeout(() => {
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
}, 300);
}
}, 250);
}
}
};
}
calendarGrid.appendChild(dayCell);
});
calendarContainer.appendChild(calendarGrid);
};
renderCalendar();