Skip to content

Add Calendar page #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions calendar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
---
layout: default
title: Calendar
---
<article class="container card">
<div class="card-body">
<h1 class="card-title">Calendar <a href="/feeds/calendar.ics" target="_blank"><span class="far fa-calendar-alt icon-orange"></span></a></h1>
<div id="menu">
<span id="menu-navi">
<button type="button" class="btn btn-default btn-sm move-today" data-action="move-today">Today</button>
<button type="button" class="btn btn-default btn-sm move-day" data-action="move-prev">
<i class="fas fa-chevron-left" data-action="move-prev"></i>
</button>
<button type="button" class="btn btn-default btn-sm move-day" data-action="move-next">
<i class="fas fa-chevron-right" data-action="move-next"></i>
</button>
</span>
<span id="renderRange" class="render-range"></span>
</div>
<br>

<div id="calendar"></div>
<br>
</div>
</article>

<link rel="stylesheet" type="text/css" href="https://uicdn.toast.com/tui-calendar/latest/tui-calendar.css" />
<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 'uid':
// obj.id = j[3];
// break;
case 'summary':
// Remove prefix added in iCal generation
obj.title = (j[3].startsWith('RITlug: ') ? j[3].substring(8) : 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>
5 changes: 3 additions & 2 deletions feeds/calendar.ics
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//{{site.title}}//{{site.url}}//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH{% for post in site.posts %}{% if post.categories.last == "meetings-meetups" or post.categories.first == "events" %}
METHOD:PUBLISH{% for post in site.posts %}{% if post.categories.last == "meetings-meetups" or post.categories.first == "events" or post.categories.first == "talks" or post.categories.first == "announcements" and post.layout == "post-event" %}
BEGIN:VEVENT
UID:{{site.url}}{{site.baseurl}}{{post.url}}
DTSTAMP:{{ post.date | date: "%Y%m%dT000000Z" }}{% if post.date-start %}
DTSTART;TZID=America/New_York:{{ post.date-start | date: "%Y%m%dT%H%M00" }}{% else %}
DTSTART:{{ post.date | date: "%Y%m%d" }}{% endif %}{% if post.date-end %}
DTEND;TZID=America/New_York:{{ post.date-end | date: "%Y%m%dT%H%M00" }}{% else %}
DTEND:{{ post.date | date: "%Y%m%d" }}{% endif %}
DTEND:{{ post.date | date: "%Y%m%d" }}{% endif %}{% if post.rrule %}
RRULE:{{post.rrule}}{% endif %}
ORGANIZER;CN="{{site.title}}":MAILTO:{{site.email}}
SUMMARY:{{post.title}}
DESCRIPTION:{{post.excerpt | strip_html | newline_to_br | replace: "<br />", " " | strip_newlines | strip}}
Expand Down
10 changes: 10 additions & 0 deletions meetings-meetups/_posts/2020-01-16-foss-hours.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
layout: redirect
redirect: https://fossrit.github.io/
date-start: "2020-01-16 17:00"
date-end: "2020-01-16 19:00"
location: "MSS-3190"
title: "FOSS Hours"
rrule: "FREQ=WEEKLY;UNTIL=20200423T190000"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the syntax for this frontmatter? I want to link out to documentation about how to write these for future contributors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwflory https://github.com/RITlug/ritlug.github.io#calendar--placeholders

Placeholders can also take an rrule frontmatter for recurring events, written to the iCal spec.

Of particular note,

The UNTIL rule part defines a DATE or DATE-TIME value that bounds the recurrence rule in an inclusive manner. If the value specified by UNTIL is synchronized with the specified recurrence, this DATE or DATE-TIME becomes the last instance of the recurrence. The value of the UNTIL rule part MUST have the same value type as the "DTSTART" property.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ct-martin Thanks. I'll use that as a base for the Runbook.

---
Come meet other students and faculty involved with FOSS efforts at RIT!