let notes = [];
let goals = [];
let editingNoteId = null;
let filterTimer = null;
let currentUser = null;
let savedGoalId = null;
async function loadNotes() {
const goalId = document.getElementById("filter-goal").value;
const search = document.getElementById("filter-search").value.trim();
let params = new URLSearchParams();
if (goalId) params.set("goal_id", goalId);
if (search) params.set("search", search);
try {
notes = await get(`/api/notes?${params.toString()}`);
renderNotes();
} catch (error) {
console.error("Failed to load notes:", error);
}
}
async function loadGoals() {
try {
goals = await get("/api/goals");
currentUser = await get("/api/auth/me");
savedGoalId = currentUser.selected_goal_id;
populateGoalSelectors();
} catch (error) {
console.error("Failed to load goals:", error);
}
}
function populateGoalSelectors() {
const filterSelect = document.getElementById("filter-goal");
const modalSelect = document.getElementById("note-goal");
const activated = goals.filter(g => g.activated);
const options = activated.map(g =>
``
).join("");
const savedGoalExists = savedGoalId && activated.some(g => g.id === savedGoalId);
filterSelect.innerHTML = `` + options;
if (savedGoalExists) {
filterSelect.value = savedGoalId;
}
modalSelect.innerHTML = `` + options;
}
function renderNotes() {
const container = document.getElementById("notes-list");
if (notes.length === 0) {
container.innerHTML = `
No notes yet
Create your first note to get started!
`;
return;
}
container.innerHTML = notes.map(note => {
const snippet = (note.content || "").replace(/[#*`\[\]()>|~_-]/g, "").substring(0, 120);
const time = formatTime(note.updated_at);
let link = "";
if (note.task_title) {
link = `${escapeHtml(note.goal_title)} / ${escapeHtml(note.task_title)}`;
} else if (note.goal_title) {
link = `${escapeHtml(note.goal_title)}`;
}
return `
${escapeHtml(note.title)}
${time}
${link}
${escapeHtml(snippet)}
`;
}).join("");
}
async function openNote(noteId) {
editingNoteId = noteId;
const note = notes.find(n => n.id === noteId);
if (!note) return;
document.getElementById("note-modal-title").textContent = "Edit Note";
document.getElementById("note-id").value = note.id;
document.getElementById("note-title").value = note.title;
document.getElementById("note-content").value = note.content || "";
document.getElementById("note-goal").value = note.goal_id || "";
document.getElementById("note-error").textContent = "";
await populateTasks(note.goal_id);
document.getElementById("note-task").value = note.task_id || "";
document.getElementById("delete-note-btn").style.display = "inline-block";
updatePreview();
document.getElementById("note-modal").classList.add("active");
}
async function openNewNote() {
editingNoteId = null;
document.getElementById("note-modal-title").textContent = "New Note";
document.getElementById("note-id").value = "";
document.getElementById("note-title").value = "";
document.getElementById("note-content").value = "";
document.getElementById("note-goal").value = savedGoalId || "";
document.getElementById("note-error").textContent = "";
document.getElementById("delete-note-btn").style.display = "none";
updatePreview();
document.getElementById("note-modal").classList.add("active");
if (savedGoalId) {
await populateTasks(savedGoalId);
const goal = goals.find(g => g.id === savedGoalId);
if (goal && goal.selected_task_id) {
document.getElementById("note-task").value = goal.selected_task_id;
}
} else {
document.getElementById("note-task").innerHTML = '';
}
}
function closeNoteModal() {
document.getElementById("note-modal").classList.remove("active");
editingNoteId = null;
}
async function populateTasks(goalId) {
const select = document.getElementById("note-task");
select.innerHTML = '';
if (!goalId) return;
try {
const tasks = await get(`/api/tasks?goal_id=${goalId}`);
select.innerHTML += tasks.map(t =>
``
).join("");
} catch (error) {
console.error("Failed to load tasks:", error);
}
}
function updatePreview() {
const content = document.getElementById("note-content").value;
const preview = document.getElementById("note-preview");
try {
preview.innerHTML = marked.parse(content || "");
} catch (e) {
preview.innerHTML = escapeHtml(content || "");
}
}
async function handleNoteSubmit(event) {
event.preventDefault();
const error = document.getElementById("note-error");
error.textContent = "";
const title = document.getElementById("note-title").value.trim();
if (!title) {
error.textContent = "Title is required";
return;
}
const goalId = parseInt(document.getElementById("note-goal").value) || null;
const taskId = parseInt(document.getElementById("note-task").value) || null;
const content = document.getElementById("note-content").value;
try {
if (editingNoteId) {
await put(`/api/notes/${editingNoteId}`, { title, content });
} else {
await post("/api/notes", { goal_id: goalId, task_id: taskId, title, content });
}
closeNoteModal();
await loadNotes();
} catch (err) {
error.textContent = err.message;
}
}
async function deleteNote() {
if (!editingNoteId) return;
if (!confirm("Delete this note?")) return;
try {
await del(`/api/notes/${editingNoteId}`);
closeNoteModal();
await loadNotes();
} catch (error) {
console.error("Failed to delete note:", error);
}
}
function formatTime(isoString) {
if (!isoString) return "";
const date = new Date(isoString);
return date.toLocaleString();
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("create-note-btn").addEventListener("click", openNewNote);
document.getElementById("note-modal-close").addEventListener("click", closeNoteModal);
document.getElementById("note-modal-cancel").addEventListener("click", closeNoteModal);
document.getElementById("note-form").addEventListener("submit", handleNoteSubmit);
document.getElementById("delete-note-btn").addEventListener("click", deleteNote);
document.getElementById("note-content").addEventListener("input", updatePreview);
document.getElementById("note-goal").addEventListener("change", async (e) => {
const goalId = parseInt(e.target.value) || null;
savedGoalId = goalId;
await patch("/api/user/selected-goal", { goal_id: goalId });
populateTasks(goalId);
});
document.getElementById("filter-goal").addEventListener("change", () => {
loadNotes();
});
document.getElementById("filter-search").addEventListener("input", () => {
clearTimeout(filterTimer);
filterTimer = setTimeout(loadNotes, 300);
});
loadGoals();
loadNotes();
});