Skip to content
This repository has been archived by the owner on Sep 16, 2022. It is now read-only.

Latest commit

 

History

History
197 lines (174 loc) · 8.23 KB

calendar.md

File metadata and controls

197 lines (174 loc) · 8.23 KB
layout title
default
Calendar

Calendar

Today < Previous Month Next Month >

    <div id="calendar"></div>
    <br>
</div>
<script src="https://uicdn.toast.com/tui.code-snippet/latest/tui-code-snippet.js"></script> <script src="https://uicdn.toast.com/tui.dom/v3.0.0/tui-dom.js"></script> <script src="https://uicdn.toast.com/tui-calendar/latest/tui-calendar.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.3.0/ical.min.js" integrity="sha256-1oaCObP6yNz1ouAW50FOpNBBSNqZtpINrbL8B4W+kmM=" crossorigin="anonymous"></script> <script> // Load iCal feed (generated by Jekyll) fetch('/feeds/calendar.ics').then((res) => { // If fails to load, error and stop if(!res.ok) { document.querySelector('#calendar').innerText = 'Could not load feed (request failed)'; return; }
// Get body of request
res.text().then((str) => {
    try {
        // Parse with ical.js
        let parsed = ICAL.parse(str);

        // Helpers for month text
        const monthText = document.querySelector('.render-range');
        const updateMonthText = () => {
            monthText.innerText = new Intl.DateTimeFormat('en-US', { month: 'long', year: 'numeric' }).format(calendar.getDate().toDate());
        }

        // Initialize calendar
        let calendar = new tui.Calendar('#calendar', {
            defaultView: 'month',
            isReadOnly: true,
            usageStatistics: false,
            useCreationPopup: false,
            useDetailPopup: true
        });

        // Bind buttons to calendar
        document.querySelector('[data-action="move-today"]').addEventListener('click', () => { calendar.today(); updateMonthText(); });
        document.querySelector('[data-action="move-next"]').addEventListener('click', () => { calendar.next(); updateMonthText(); });
        document.querySelector('[data-action="move-prev"]').addEventListener('click', () => { calendar.prev(); updateMonthText(); });
        updateMonthText();

        // Array of events to eventually be loaded into the calendar
        let evtArr = [];

        // Iterate through events from iCal feed
        parsed[2].forEach((i, no) => {
            // Define base parameters
            let obj = {
                bgColor: 'black',
                body: '',
                category: 'time',
                color: 'white',
                id: `${no}`,
                isReadOnly: true,
                isVisible: true
            };

            // Define vars for special cases
            let rrule = false;
            let url = '';

            // Iterate through parameters and map to object properties
            // (Convert iCal property names to tui.calendar property names)
            i[1].forEach((j) => {
                switch (j[0]) {
                    case 'summary':
                        obj.title = j[3];
                        break;
                    case 'description':
                        obj.body = j[3];
                        break;
                    case 'dtstart':
                        // ical.js adds empty time strings if an all-day event
                        // strip these and set all-day properties instead if needed
                        if(j[3].endsWith('T::')) {
                            obj.category = 'allday';
                            obj.isAllDay = true;
                            obj.start = j[3].replace('T::','');
                        } else {
                            obj.start = j[3];
                        }
                        break;
                    case 'dtend':
                        // Strip empty time strings if needed (see dtstart comments)
                        obj.end = j[3].replace('T::','');
                        break;
                    case 'location':
                        obj.location = j[3];
                        break;
                    case 'rrule':
                        // RRULE will get interpreted from object later
                        // Just set special case var to trigger parsing later
                        rrule = true;
                        break;
                    case 'url':
                        url = j[3];
                        break;
                }
            });

            // If has URL, append to body (since no field for in details popup)
            if(url !== '') {
                obj.body += `${(obj.body === '' ? '' : '<br>')}<a href="${url}">${url}</a>`;
            }

            // tui.calendar can't handle RRULEs (recurrence rules)
            // So, if an RRULE is set, use ical.js to expand occurances manually
            if(rrule) {
                // Create ical.js component from jCal output of parse
                let comp = new ICAL.Component(i);

                // Figure out the duration of the event (since can't directly expand start & end at the same time)
                let duration = ICAL.Time.fromString(obj.end).subtractDate(ICAL.Time.fromString(obj.start));

                // Create RRULE Expansion with ical.js
                // (Creates an interable)
                let expand = new ICAL.RecurExpansion({
                    component: comp,
                    dtstart: comp.getFirstPropertyValue('dtstart')
                });

                // Counter to prevent infinite iteration of RRULE (since by spec is allowed)
                let count = 0;

                // next defined here b/c of block scoping
                let next;

                // Iterate through occurances
                // Arbitrary limit of 25 is for infinite iteration prevention
                while(count < 25 && (next = expand.next())) {
                    // Increment infinite iteration prevention counter
                    count++;

                    // Duplicate event with RRULE
                    let o = {};
                    Object.assign(o, obj);

                    // Give unique ID
                    o.id += `::RRULE-n${count}`;

                    // Set start to this occurance from RRULE expansion
                    o.start = next.toString();

                    // Reconstruct end from duration and set
                    let end = ICAL.Time.fromJSDate(next.toJSDate());
                    end.addDuration(duration);
                    o.end = end.toString();

                    // Add to events array
                    evtArr.push(o);
                }
            } else {
                // If no RRULE, directly add to events array
                evtArr.push(obj);
            }
        });

        // Add events in array to calendar
        calendar.createSchedules(evtArr);
    } catch(err) {
        // Error on parsing fail
        document.querySelector('#calendar').innerText = `Could not parse: ${err}`;
        return;
    }
}).catch(() => {
    // Error on load fail
    document.querySelector('#calendar').innerText = 'Could not load feed (no body)';
    return;
});

}); </script>