js

// ---- Options -------------------------------------------

// Edit names for localization
const months = [
    "January", "February", "March", "April", "May", "June", "July",
    "August", "September", "October", "November", "December"
];
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

// Toggle displaying the current AND next month together
const doubleMonthView = true;

// Add or change events here. Array format: [ Name, icon, icon color ]
const events = [
    ["Test", "bxs-pencil", "#ff4c4c"],
    ["Deadline", "bxs-time", "#ff7b42"],
    ["Meeting", "bxs-briefcase-alt-2", "#dbc745"],
    ["Holiday", "bxs-flag-alt", "#57c94f"],
    ["Birthday", "bxs-cake", "#dd5594"],
    ["Event", "bxs-calendar-alt", "#4bb4cd"],
    ["Note", "bxs-info-circle", "#888"]
]


// ---- Code ----------------------------------------------

const today = new Date();
var currentMonth = today.getMonth();
var currentYear = today.getFullYear();

// Create header row for days of the week
var dayHeader = "<tr>";
for (let day in days) {
    dayHeader += "<th>" + days[day] + "</th>";
}
dayHeader += "</tr>";
document.getElementById("thead-month").innerHTML = dayHeader;

// Calendar buttons
document.getElementById("previous").addEventListener("click", previous);
document.getElementById("next").addEventListener("click", next);
document.getElementById("month").addEventListener("change", jump);
document.getElementById("year").addEventListener("change", jump);

var yearDropdown = "";
for (var i = currentYear-20; i <= currentYear+20; i++) {
    yearDropdown += "<option value='" + i + "'>" + i + "</option>";
}
document.getElementById("year").innerHTML = yearDropdown;

// Get needed HTML elements
const selectYear = document.getElementById("year");
const selectMonth = document.getElementById("month");
const calendarTitle = document.getElementById("monthAndYear");
const table = document.getElementById("calendar-body");


showCalendar(currentMonth, currentYear);



// Displays the given month and year on the calendar.
// Month starts at zero. Both args are numbers.
async function showCalendar(month, year) {
    
    // Used to determine which day of the week the month starts on
    const firstDay = (new Date(year, month)).getDay();
    
    // Used for the double month view.
    // nextYear refers to what year it is next month.
    const nextMonth = (month + 1) % 12;  
    const nextYear = (month === 11) ? year + 1 : year;
    
    // Update the table with new values
    table.innerHTML = "";
    selectYear.value = year;
    selectMonth.value = month;
    if (doubleMonthView) {
        calendarTitle.innerHTML = months[month] + " " + year + 
        "<span style='font-size: 65%'> / " + months[nextMonth] + " " + nextYear+ "</span>";
    } else {
        calendarTitle.innerHTML = months[month] + " " + year;
    }
    
    // Create arrays that will contain the month's day notes 
    // and their attributes.    
    var monthArray = [];
    var attributeArray = [];
    await makeNoteArrays(month, year, monthArray, attributeArray);
    
    if (doubleMonthView) {
        var nextMonthArray = [];
        var nextAttributeArray = [];
        await makeNoteArrays(nextMonth, nextYear, nextMonthArray, nextAttributeArray);
    }

    var date = 1;
    var weekMax = 12;
    var stop = false;
    // Loop for each week (make a table row)
    for (var i = 0; i < weekMax && !stop; i++) {
        
        var row = document.createElement("tr");
        
        // Loop for each day (make a cell for every day)
        for (var j = 0; j < 7 && !stop; j++) {
            
            var cell;
            
            // Cells before 1st of the month (blank square)
            if (i === 0 && j < firstDay) { 
                cell = createBlankCell();
            } 
            // Cells of next month (only drawn if enabled)
            else if (date > daysInMonth(month, year)) {
                if (doubleMonthView) {
                    var nextDate = date - daysInMonth(month, year);

                    // Cells of next NEXT month
                    if (nextDate > daysInMonth(nextMonth, nextYear)) {
                        // Don't draw a completely empty week
                        if (j===0) {
                            stop = true;
                            continue;
                        }
                        cell = createBlankCell();
                        row.appendChild(cell);
                        weekMax = i;
                        continue;
                    }

                    cell = createCell(nextDate, nextMonth, nextYear);
                    applyLink(cell, nextDate, nextMonth, nextYear, nextMonthArray);
                    stylizeCell(cell, nextDate, nextMonthArray, nextAttributeArray);
                    date++;
                } else {
                    // Don't draw a completely empty week
                    if (j===0) {
                        stop = true;
                        continue;
                    }
                    cell = createBlankCell();
                    weekMax = i;
                }
            } 
            // Cells of this month
            else {
                cell = createCell(date, month, year);
                applyLink(cell, date, month, year, monthArray);
                stylizeCell(cell, date, monthArray, attributeArray);
                
                // If current cell is today
                if (date === today.getDate() && year === today.getFullYear() 
                    && month === today.getMonth()) {
                    cell.className = "date-picker selected";
                }

                row.appendChild(cell);
                date++;
            }
            
            row.appendChild(cell);
            
        } // End of day
        
        table.appendChild(row);
        
    } // End of week
}

// Populates monthArray with all existing day notes in that month. Each 
// element's index represents the day. Ex: March 5th is monthArray[5], 
// which would contain a note ID if the page exists.
//
// Also populates attributeArray with any events for the day.
async function makeNoteArrays(month, year, monthArray, attributeArray) {
    
    var monthNote = await api.getMonthNote((month < 9) ? year+"-0"+(month+1) : year+"-"+(month+1) );
    var monthChildren = await monthNote.getChildNotes();
    
    for (let dayNote of monthChildren) {
        var indexDay = Number(dayNote.getLabel("dateNote").value.substring(8, 10)); 
        monthArray[indexDay] = dayNote.noteId;
        
        // Look for events defined by global array (line 15)
        attributeArray[indexDay] = [];
        for (let event of events) {            
            attributeArray[indexDay].push(dayNote.getLabels(event[0]));
        }
        
        
    } 
}

// Returns an empty table cell for squares with no date.
function createBlankCell() {
    var cell = document.createElement("td");
    var cellText = document.createTextNode("");
    cell.className = "date-picker"
    cell.appendChild(cellText);
    return cell;
}

// Returns a cell with the given date.
function createCell(date, month, year) {
    var cell = document.createElement("td");
    cell.setAttribute("data-date", date);
    cell.setAttribute("data-month", month + 1);
    cell.setAttribute("data-year", year);
    cell.setAttribute("data-month_name", months[month]);
    cell.className = "date-picker";
    return cell;
}

// Adds the day number to the cell. Clicking it activates or creates
// the respective day note.
function applyLink(cell, date, month, year, monthArray) {
    
    // Create a string in the format YYYY-MM-DD 
    if (month < 9) {
      var todayStr = year + "-0" + (month + 1);
    } else {
      var todayStr = year + "-" + (month + 1);
    }
    if (date < 10) {
        todayStr = todayStr + "-0" + date;
    } else {
        todayStr = todayStr + "-" + date;
    }
    
    // Use string for 'calendar-date' HTML attribute, which gives the
    // ability to create a day note by clicking the link.
    var link = document.createElement("a");
    link.setAttribute("calendar-date", todayStr);
    link.innerHTML = date;
    link.addEventListener("click", goToDay);
    link.className = 'day-number';
    cell.appendChild(link);
   
    // If a note already exists for this day, make a link to it.
    if (monthArray[date] != undefined) {
        link.setAttribute("data-note-path", monthArray[date]);
    }
    
}

// Looks through the attribute array to create any neccessary event markers
// on the given calendar cell.
function stylizeCell(cell, date, monthArray, attributeArray) {
    
    // Stop if there's no day note on this date
    if (monthArray[date] == undefined) {
        return;
    }
    
    // 'attribute' contains a two-dimensional array.
    // First array dictates which event type it is, second contains event descriptions.
    var attribute = attributeArray[date];
    
    for(let i = 0; i < attribute.length; i++) {
        for(let j = 0; j < attribute[i].length; j++) {
            
            if(attribute[i][j].value != "") {
                var marker = document.createElement("div");
                marker.classList.add("marker");
                var markerIcon = document.createElement("span");
                markerIcon.classList.add("bx");
                
                // Icon is created by adding Box Icon class (see line 15)
                markerIcon.classList.add(events[i][1]);
                markerIcon.classList.add("marker-icon");
                markerIcon.style.color = events[i][2];
                
                marker.appendChild(markerIcon);
                marker.appendChild(document.createTextNode(" "+ attribute[i][j].value ))
                
                // Hover tooltip
                var tooltip = document.createElement("span");
                tooltip.classList.add("marker-tooltip");
                tooltip.appendChild(document.createTextNode(attribute[i][j].value));
                marker.appendChild(tooltip);
                
                cell.appendChild(marker);   
            }
        }  
    }
}

// Returns how many days there are in the given month
function daysInMonth(iMonth, iYear) {
    return 32 - new Date(iYear, iMonth, 32).getDate();
}


// ---- Calendar event handlers ---------------------------

// View the next month
function next() {
    currentYear = (currentMonth === 11) ? currentYear + 1 : currentYear;
    currentMonth = (currentMonth + 1) % 12;
    showCalendar(currentMonth, currentYear);
}

// View the previous month
function previous() {
    currentYear = (currentMonth === 0) ? currentYear - 1 : currentYear;
    currentMonth = (currentMonth === 0) ? 11 : currentMonth - 1;
    showCalendar(currentMonth, currentYear);
}

// View a month specified from the calendar controls
function jump() {
    currentYear = parseInt(selectYear.value);
    currentMonth = parseInt(selectMonth.value);
    showCalendar(currentMonth, currentYear);
}

// Activate the clicked day note
async function goToDay() {
    var day = await api.getDateNote(event.currentTarget.getAttribute("calendar-date"));
    api.activateNote(day.noteId);
}