π Study Session Tracker
π Study Session Tracker
An afternoon in the library, a warm cup of coffee, and a little progress at a time. βπΏ
π―οΈ A Gentle Reminder
You don't have to finish everything today. A little progress is still progress.
πΈ Welcome to Your Study Journal
// ==========================================
// πΏ STUDY NOOK β COZY STUDY DASHBOARD
// ==========================================
// π Our correct Study Nook folder
const folder = '"study nook/School/Study Sessions"';
const sessions = dv.pages(folder)
.where(p => p.type === "study-session")
.array()
.filter(p => p.date && Number(p.minutes) > 0);
// π―οΈ Dates
const today = dv.date("today");
const monday = today.minus({
days: today.weekday - 1
});
const sunday = monday.plus({ days: 6 });
function sessionDate(p) {
try {
return dv.date(p.date);
} catch {
return null;
}
}
function minutesTotal(items) {
return items.reduce(
(sum, p) => sum + Number(p.minutes), 0
);
}
function prettyTime(n) {
const h = Math.floor(n / 60);
const m = Math.round(n % 60);
if (h === 0) return `${m} min`;
if (m === 0) return `${h}h`;
return `${h}h ${m}m`;
}
// β Study totals
const todaySessions = sessions.filter(p => {
const d = sessionDate(p);
return d && d.toISODate() === today.toISODate();
});
const weekSessions = sessions.filter(p => {
const d = sessionDate(p);
return d && d >= monday && d <= sunday;
});
const cards = [
["β Today", prettyTime(minutesTotal(todaySessions))],
["πΏ This Week", prettyTime(minutesTotal(weekSessions))],
["π Sessions", String(sessions.length)],
["π―οΈ All Time", prettyTime(minutesTotal(sessions))]
];
// πΈ Create cozy statistic cards
const grid = dv.container.createDiv();
grid.style.display = "grid";
grid.style.gridTemplateColumns =
"repeat(auto-fit, minmax(145px, 1fr))";
grid.style.gap = "14px";
grid.style.margin = "18px 0 24px";
for (const [label, value] of cards) {
const card = grid.createDiv();
card.style.background = "var(--background-secondary)";
card.style.border =
"1px solid var(--background-modifier-border)";
card.style.borderRadius = "14px";
card.style.padding = "20px 12px";
card.style.textAlign = "center";
card.style.boxShadow =
"0 3px 10px rgba(55, 48, 30, 0.05)";
const heading = card.createDiv();
heading.setText(label);
heading.style.fontSize = "0.9em";
heading.style.color = "var(--text-muted)";
const number = card.createDiv();
number.setText(value);
number.style.fontSize = "1.65em";
number.style.fontWeight = "650";
number.style.color = "var(--text-normal)";
number.style.marginTop = "8px";
}
// πΏ Working button
const buttonArea = dv.container.createDiv();
buttonArea.style.textAlign = "center";
buttonArea.style.margin = "18px 0 12px";
const button = buttonArea.createEl("button", {
text: "πΈ Log a Study Session"
});
button.style.background = "#718a6c";
button.style.color = "#ffffff";
button.style.padding = "12px 25px";
button.style.borderRadius = "12px";
button.style.fontWeight = "600";
button.style.border = "none";
button.style.cursor = "pointer";
// πΈ Open our Study Session template directly
button.addEventListener("click", async () => {
const templatePath =
"study nook/School/Templates/New Study Session.md";
const destinationFolder =
"study nook/School/Study Sessions";
// Find our specific template
const templateFile =
app.vault.getAbstractFileByPath(templatePath);
if (!templateFile) {
new Notice("πΏ Study Session template not found!");
return;
}
// Connect to Templater
const templater =
app.plugins.plugins["templater-obsidian"]?.templater;
if (!templater) {
new Notice("πΏ Please enable Templater.");
return;
}
try {
// Temporary unique filename.
// Our template will rename it automatically.
const temporaryName =
`Study Session Draft ${Date.now()}`;
await templater.create_new_note_from_template(
templateFile,
destinationFolder,
temporaryName,
true
);
} catch (error) {
console.error("Study Nook:", error);
new Notice("πΏ Could not create study session.");
}
});
const hint = buttonArea.createDiv();
hint.setText(
"A few minutes or a whole afternoon β every session counts."
);
hint.style.fontStyle = "italic";
hint.style.fontSize = "0.82em";
hint.style.color = "var(--text-muted)";
hint.style.marginTop = "10px";
// π· Gentle encouragement
const message = dv.container.createDiv();
message.style.textAlign = "center";
message.style.margin = "18px 0";
message.style.color = "var(--text-muted)";
message.style.fontStyle = "italic";
if (sessions.length === 0) {
message.setText(
"π± Your study garden is waiting for its first little seed."
);
} else if (todaySessions.length === 0) {
message.setText(
"π―οΈ Your journal is here whenever you're ready."
);
} else {
message.setText(
"πΈ Look at the progress you've made today!"
);
}
ποΈ A Week of Little Victories
Seven little days, each with room to grow. π±
// ==========================================
// πΏ STUDY NOOK β WEEKLY STUDY CALENDAR
// ==========================================
// π Our correct study session folder
const sessions = dv.pages('"study nook/School/Study Sessions"')
.where(p => p.type === "study-session")
.array()
.filter(p => p.date && Number(p.minutes) > 0);
// π
Find the current Monday through Sunday
const today = dv.date("today");
const monday = today.minus({
days: today.weekday - 1
});
const sunday = monday.plus({ days: 6 });
// πΈ Collect study minutes by date
const dailyTotals = {};
for (const session of sessions) {
const date = dv.date(session.date);
if (!date) continue;
const key = date.toISODate();
dailyTotals[key] =
(dailyTotals[key] || 0) + Number(session.minutes);
}
// β Format study time
function formatTime(minutes) {
const h = Math.floor(minutes / 60);
const m = Math.round(minutes % 60);
if (h === 0) return `${m}m`;
return m === 0 ? `${h}h` : `${h}h ${m}m`;
}
// π―οΈ Week title
const weekTitle = dv.container.createDiv();
weekTitle.setText(
`πΈ ${monday.toFormat("MMMM d")} β ` +
`${sunday.toFormat("MMMM d, yyyy")}`
);
weekTitle.style.textAlign = "center";
weekTitle.style.fontSize = "1.05em";
weekTitle.style.fontWeight = "600";
weekTitle.style.margin = "16px 0";
weekTitle.style.color = "var(--text-normal)";
// πΏ Create calendar grid
const calendar = dv.container.createDiv();
calendar.style.display = "grid";
calendar.style.gridTemplateColumns =
"repeat(7, minmax(0, 1fr))";
calendar.style.gap = "6px";
calendar.style.margin = "15px 0";
// π Day labels
const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
let weeklyMinutes = 0;
let studyDays = 0;
for (let i = 0; i < 7; i++) {
const date = monday.plus({ days: i });
const key = date.toISODate();
const minutes = dailyTotals[key] || 0;
const isToday = key === today.toISODate();
const hasStudied = minutes > 0;
weeklyMinutes += minutes;
if (hasStudied) studyDays++;
// πΈ Day card
const card = calendar.createDiv();
card.style.display = "flex";
card.style.flexDirection = "column";
card.style.alignItems = "center";
card.style.justifyContent = "center";
card.style.gap = "5px";
card.style.padding = "12px 2px";
card.style.minWidth = "0";
card.style.borderRadius = "12px";
card.style.background = hasStudied
? "rgba(129, 157, 111, 0.17)"
: "var(--background-secondary)";
card.style.border = isToday
? "2px solid #819d6f"
: "1px solid var(--background-modifier-border)";
// Day of week
const dayLabel = card.createDiv();
dayLabel.setText(days[i]);
dayLabel.style.fontSize = "0.75em";
dayLabel.style.color = "var(--text-muted)";
// Calendar date
const number = card.createDiv();
number.setText(String(date.day));
number.style.fontWeight = "650";
number.style.fontSize = "1.15em";
// π± Little plant marker
const flower = card.createDiv();
flower.setText(hasStudied ? "π±" : "Β·");
flower.style.fontSize = "1.05em";
// β Minutes studied
const time = card.createDiv();
time.setText(
hasStudied ? formatTime(minutes) : "β"
);
time.style.fontSize = "0.72em";
time.style.color = "var(--text-muted)";
time.style.textAlign = "center";
}
// π· Weekly summary
const summary = dv.container.createDiv();
summary.style.display = "grid";
summary.style.gridTemplateColumns = "1fr 1fr";
summary.style.gap = "12px";
summary.style.margin = "20px 0";
const summaryCards = [
["β This Week's Study Time",
formatTime(weeklyMinutes)],
["πͺ» Days with Study Sessions",
`${studyDays} of 7`]
];
for (const [label, value] of summaryCards) {
const card = summary.createDiv();
card.style.background = "var(--background-secondary)";
card.style.border =
"1px solid var(--background-modifier-border)";
card.style.borderRadius = "12px";
card.style.padding = "15px";
card.style.textAlign = "center";
const heading = card.createDiv();
heading.setText(label);
heading.style.fontSize = "0.8em";
heading.style.color = "var(--text-muted)";
const number = card.createDiv();
number.setText(value);
number.style.fontWeight = "650";
number.style.fontSize = "1.3em";
number.style.marginTop = "8px";
}
// πͺ· Closing message
const note = dv.container.createDiv();
note.setText(
"πΏ Every little leaf represents a moment of learning."
);
note.style.textAlign = "center";
note.style.fontStyle = "italic";
note.style.color = "var(--text-muted)";
note.style.fontSize = "0.85em";
note.style.margin = "16px 0";
πΏ My Study Garden
A little collection of all the time you've spent learning.
const sessions = dv.pages('"study nook/School/Study Sessions"')
.where(p => p.type === "study-session")
.array()
.filter(p => Number(p.minutes) > 0);
const totals = {};
for (const session of sessions) {
const name = String(session.class || "Other");
totals[name] =
(totals[name] || 0) + Number(session.minutes);
}
const entries = Object.entries(totals)
.sort((a, b) => b[1] - a[1]);
const total = entries.reduce(
(sum, item) => sum + item[1], 0
);
const colors = [
"#8a9b74",
"#b69b79",
"#ac91a4",
"#8eaaa5",
"#c3ae78"
];
function prettyTime(n) {
const h = Math.floor(n / 60);
const m = Math.round(n % 60);
if (h === 0) return `${m} min`;
return m ? `${h}h ${m}m` : `${h}h`;
}
if (!entries.length) {
dv.paragraph(
"π± Your subjects will begin blooming here soon."
);
} else {
const garden = dv.container.createDiv();
garden.style.margin = "18px 0";
entries.forEach(([name, minutes], index) => {
const item = garden.createDiv();
item.style.marginBottom = "18px";
const heading = item.createDiv();
heading.style.display = "flex";
heading.style.justifyContent = "space-between";
heading.style.gap = "12px";
heading.style.marginBottom = "7px";
const title = heading.createSpan();
title.setText("π± " + name);
const time = heading.createSpan();
time.setText(prettyTime(minutes));
time.style.color = "var(--text-muted)";
const track = item.createDiv();
track.style.height = "12px";
track.style.borderRadius = "10px";
track.style.background =
"var(--background-modifier-border)";
track.style.overflow = "hidden";
const fill = track.createDiv();
fill.style.height = "100%";
fill.style.width =
`${(minutes / total) * 100}%`;
fill.style.borderRadius = "10px";
fill.style.background =
colors[index % colors.length];
});
const footnote = garden.createDiv();
footnote.setText(
"πͺ» Each bar shows a class's share of your total study time."
);
footnote.style.fontSize = "0.85em";
footnote.style.fontStyle = "italic";
footnote.style.color = "var(--text-muted)";
}
π Recent Pages in My Journal
Every study session has its own little story.
const sessions = dv.pages('"study nook/School/Study Sessions"')
.where(p => p.type === "study-session")
.sort(p => p.file.ctime, "desc")
.limit(10);
const rows = sessions.map(p => [
dv.fileLink(p.file.path, false,
p.date
? dv.date(p.date).toFormat("MMM d, yyyy")
: p.file.name
),
p.class || "Other",
`${p.minutes || 0} min`,
p.accomplishment || "π A little progress!"
]);
if (rows.length) {
dv.table(
["π
Date", "π Class", "β Time", "πΈ Accomplished"],
rows
);
} else {
dv.paragraph(
"π―οΈ Your first journal entry is waiting."
);
}
πΈ A Little Progress Is Still Progress
Whether you studied for five minutes or two hours, you showed up for yourself today. That's something worth remembering.
May your coffee stay warm and your curiosity keep growing. βπΏπ