mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-27 19:53:36 +00:00
Events with the class:PRIVATE iCal property are not shown in the calendar module. They are not added to the array of calendar events in createEventList. This feature can be turned on and off in the config via the property hidePrivate: true/false.
376 lines
12 KiB
JavaScript
376 lines
12 KiB
JavaScript
/* global Module */
|
|
|
|
/* Magic Mirror
|
|
* Module: Calendar
|
|
*
|
|
* By Michael Teeuw http://michaelteeuw.nl
|
|
* MIT Licensed.
|
|
*/
|
|
|
|
Module.register("calendar",{
|
|
|
|
// Define module defaults
|
|
defaults: {
|
|
maximumEntries: 10, // Total Maximum Entries
|
|
maximumNumberOfDays: 365,
|
|
displaySymbol: true,
|
|
defaultSymbol: "calendar", // Fontawesome Symbol see http://fontawesome.io/cheatsheet/
|
|
displayRepeatingCountTitle: false,
|
|
defaultRepeatingCountTitle: '',
|
|
maxTitleLength: 25,
|
|
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
|
|
animationSpeed: 2000,
|
|
fade: true,
|
|
urgency: 7,
|
|
timeFormat: "relative",
|
|
fadePoint: 0.25, // Start on 1/4th of the list.
|
|
calendars: [
|
|
{
|
|
symbol: "calendar",
|
|
url: "http://www.calendarlabs.com/templates/ical/US-Holidays.ics",
|
|
},
|
|
],
|
|
titleReplace: {
|
|
"De verjaardag van ": "",
|
|
"'s birthday": ""
|
|
},
|
|
},
|
|
|
|
// Define required scripts.
|
|
getStyles: function() {
|
|
return ["calendar.css", "font-awesome.css"];
|
|
},
|
|
|
|
// Define required scripts.
|
|
getScripts: function() {
|
|
return ["moment.js"];
|
|
},
|
|
|
|
// Define required translations.
|
|
getTranslations: function() {
|
|
// The translations for the defaut modules are defined in the core translation files.
|
|
// Therefor we can just return false. Otherwise we should have returned a dictionairy.
|
|
// If you're trying to build your own module including translations, check out the documentation.
|
|
return false;
|
|
},
|
|
|
|
// Override start method.
|
|
start: function() {
|
|
Log.log("Starting module: " + this.name);
|
|
|
|
// Set locale.
|
|
moment.locale(config.language);
|
|
|
|
for (var c in this.config.calendars) {
|
|
var calendar = this.config.calendars[c];
|
|
calendar.url = calendar.url.replace("webcal://", "http://");
|
|
this.addCalendar(calendar.url, calendar.user, calendar.pass);
|
|
}
|
|
|
|
this.calendarData = {};
|
|
this.loaded = false;
|
|
},
|
|
|
|
// Override socket notification handler.
|
|
socketNotificationReceived: function(notification, payload) {
|
|
if (notification === "CALENDAR_EVENTS") {
|
|
if (this.hasCalendarURL(payload.url)) {
|
|
this.calendarData[payload.url] = payload.events;
|
|
this.loaded = true;
|
|
}
|
|
} else if (notification === "FETCH_ERROR") {
|
|
Log.error("Calendar Error. Could not fetch calendar: " + payload.url);
|
|
} else if (notification === "INCORRECT_URL") {
|
|
Log.error("Calendar Error. Incorrect url: " + payload.url);
|
|
} else {
|
|
Log.log("Calendar received an unknown socket notification: " + notification);
|
|
}
|
|
|
|
this.updateDom(this.config.animationSpeed);
|
|
},
|
|
|
|
// Override dom generator.
|
|
getDom: function() {
|
|
|
|
var events = this.createEventList();
|
|
var wrapper = document.createElement("table");
|
|
wrapper.className = "small";
|
|
|
|
if (events.length === 0) {
|
|
wrapper.innerHTML = (this.loaded) ? this.translate("EMPTY") : this.translate("LOADING");
|
|
wrapper.className = "small dimmed";
|
|
return wrapper;
|
|
}
|
|
|
|
for (var e in events) {
|
|
var event = events[e];
|
|
|
|
var eventWrapper = document.createElement("tr");
|
|
eventWrapper.className = "normal";
|
|
|
|
if (this.config.displaySymbol) {
|
|
var symbolWrapper = document.createElement("td");
|
|
symbolWrapper.className = "symbol";
|
|
var symbol = document.createElement("span");
|
|
symbol.className = "fa fa-" + this.symbolForUrl(event.url);
|
|
symbolWrapper.appendChild(symbol);
|
|
eventWrapper.appendChild(symbolWrapper);
|
|
}
|
|
|
|
var titleWrapper = document.createElement("td"),
|
|
repeatingCountTitle = '';
|
|
|
|
|
|
if (this.config.displayRepeatingCountTitle) {
|
|
|
|
repeatingCountTitle = this.countTitleForUrl(event.url);
|
|
|
|
if(repeatingCountTitle !== '') {
|
|
var thisYear = new Date().getFullYear(),
|
|
yearDiff = thisYear - event.firstYear;
|
|
|
|
repeatingCountTitle = ', '+ yearDiff + '. ' + repeatingCountTitle;
|
|
}
|
|
}
|
|
|
|
titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle;
|
|
titleWrapper.className = "title bright";
|
|
eventWrapper.appendChild(titleWrapper);
|
|
|
|
var timeWrapper = document.createElement("td");
|
|
//console.log(event.today);
|
|
var now = new Date();
|
|
// Define second, minute, hour, and day variables
|
|
var one_second = 1000; // 1,000 milliseconds
|
|
var one_minute = one_second * 60;
|
|
var one_hour = one_minute * 60;
|
|
var one_day = one_hour * 24;
|
|
if (event.fullDayEvent) {
|
|
if (event.today) {
|
|
timeWrapper.innerHTML = this.translate("TODAY");
|
|
} else if (event.startDate - now < one_day && event.startDate - now > 0) {
|
|
timeWrapper.innerHTML = this.translate("TOMORROW");
|
|
} else if (event.startDate - now < 2*one_day && event.startDate - now > 0) {
|
|
/*Provide ability to show "the day after tomorrow" instead of "in a day"
|
|
*if "DAYAFTERTOMORROW" is configured in a language's translation .json file,
|
|
*,which can be found in MagicMirror/translations/
|
|
*/
|
|
if (this.translate('DAYAFTERTOMORROW') !== 'DAYAFTERTOMORROW') {
|
|
timeWrapper.innerHTML = this.translate("DAYAFTERTOMORROW");
|
|
} else {
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
|
|
}
|
|
} else {
|
|
/* Check to see if the user displays absolute or relative dates with their events
|
|
* Also check to see if an event is happening within an 'urgency' time frameElement
|
|
* For example, if the user set an .urgency of 7 days, those events that fall within that
|
|
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
|
|
*
|
|
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
|
|
*/
|
|
if (this.config.timeFormat === "absolute") {
|
|
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * one_day))) {
|
|
// This event falls within the config.urgency period that the user has set
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
|
|
} else {
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").format("MMM Do");
|
|
}
|
|
} else {
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
|
|
}
|
|
}
|
|
} else {
|
|
if (event.startDate >= new Date()) {
|
|
if (event.startDate - now < 2 * one_day) {
|
|
// This event is within the next 48 hours (2 days)
|
|
if (event.startDate - now < 6 * one_hour) {
|
|
// If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
|
|
} else {
|
|
// Otherwise just say 'Today/Tomorrow at such-n-such time'
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").calendar();
|
|
}
|
|
} else {
|
|
/* Check to see if the user displays absolute or relative dates with their events
|
|
* Also check to see if an event is happening within an 'urgency' time frameElement
|
|
* For example, if the user set an .urgency of 7 days, those events that fall within that
|
|
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
|
|
*
|
|
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
|
|
*/
|
|
if (this.config.timeFormat === "absolute") {
|
|
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * one_day))) {
|
|
// This event falls within the config.urgency period that the user has set
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
|
|
} else {
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").format("MMM Do");
|
|
}
|
|
} else {
|
|
timeWrapper.innerHTML = moment(event.startDate, "x").fromNow();
|
|
}
|
|
}
|
|
} else {
|
|
timeWrapper.innerHTML = this.translate("RUNNING") + ' ' + moment(event.endDate,"x").fromNow(true);
|
|
}
|
|
}
|
|
//timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll');
|
|
//console.log(event);
|
|
timeWrapper.className = "time light";
|
|
eventWrapper.appendChild(timeWrapper);
|
|
|
|
wrapper.appendChild(eventWrapper);
|
|
|
|
// Create fade effect.
|
|
if (this.config.fade && this.config.fadePoint < 1) {
|
|
if (this.config.fadePoint < 0) {
|
|
this.config.fadePoint = 0;
|
|
}
|
|
var startingPoint = events.length * this.config.fadePoint;
|
|
var steps = events.length - startingPoint;
|
|
if (e >= startingPoint) {
|
|
var currentStep = e - startingPoint;
|
|
eventWrapper.style.opacity = 1 - (1 / steps * currentStep);
|
|
}
|
|
}
|
|
}
|
|
|
|
return wrapper;
|
|
},
|
|
|
|
/* hasCalendarURL(url)
|
|
* Check if this config contains the calendar url.
|
|
*
|
|
* argument url sting - Url to look for.
|
|
*
|
|
* return bool - Has calendar url
|
|
*/
|
|
hasCalendarURL: function(url) {
|
|
for (var c in this.config.calendars) {
|
|
var calendar = this.config.calendars[c];
|
|
if (calendar.url === url) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/* createEventList()
|
|
* Creates the sorted list of all events.
|
|
*
|
|
* return array - Array with events.
|
|
*/
|
|
createEventList: function() {
|
|
var events = [];
|
|
var today = moment().startOf("day");
|
|
for (var c in this.calendarData) {
|
|
var calendar = this.calendarData[c];
|
|
for (var e in calendar) {
|
|
var event = calendar[e];
|
|
if(this.config.hidePrivate) {
|
|
if(event.class === "PRIVATE") {
|
|
// do not add the current event, skip it
|
|
continue;
|
|
}
|
|
}
|
|
event.url = c;
|
|
event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000);
|
|
events.push(event);
|
|
}
|
|
}
|
|
|
|
events.sort(function(a, b) {
|
|
return a.startDate - b.startDate;
|
|
});
|
|
|
|
return events.slice(0, this.config.maximumEntries);
|
|
},
|
|
|
|
/* createEventList(url)
|
|
* Requests node helper to add calendar url.
|
|
*
|
|
* argument url sting - Url to add.
|
|
*/
|
|
addCalendar: function(url, user, pass) {
|
|
this.sendSocketNotification("ADD_CALENDAR", {
|
|
url: url,
|
|
maximumEntries: this.config.maximumEntries,
|
|
maximumNumberOfDays: this.config.maximumNumberOfDays,
|
|
fetchInterval: this.config.fetchInterval,
|
|
user: user,
|
|
pass: pass
|
|
});
|
|
},
|
|
|
|
/* symbolForUrl(url)
|
|
* Retrieves the symbol for a specific url.
|
|
*
|
|
* argument url sting - Url to look for.
|
|
*
|
|
* return string - The Symbol
|
|
*/
|
|
symbolForUrl: function(url) {
|
|
for (var c in this.config.calendars) {
|
|
var calendar = this.config.calendars[c];
|
|
if (calendar.url === url && typeof calendar.symbol === "string") {
|
|
return calendar.symbol;
|
|
}
|
|
}
|
|
|
|
return this.config.defaultSymbol;
|
|
},
|
|
/* countTitleForUrl(url)
|
|
* Retrieves the name for a specific url.
|
|
*
|
|
* argument url sting - Url to look for.
|
|
*
|
|
* return string - The Symbol
|
|
*/
|
|
countTitleForUrl: function(url) {
|
|
for (var c in this.config.calendars) {
|
|
var calendar = this.config.calendars[c];
|
|
if (calendar.url === url && typeof calendar.repeatingCountTitle === "string") {
|
|
return calendar.repeatingCountTitle;
|
|
}
|
|
}
|
|
|
|
return this.config.defaultRepeatingCountTitle;
|
|
},
|
|
|
|
/* shorten(string, maxLength)
|
|
* Shortens a sting if it's longer than maxLenthg.
|
|
* Adds an ellipsis to the end.
|
|
*
|
|
* argument string string - The string to shorten.
|
|
* argument maxLength number - The max lenth of the string.
|
|
*
|
|
* return string - The shortened string.
|
|
*/
|
|
shorten: function(string, maxLength) {
|
|
if (string.length > maxLength) {
|
|
return string.slice(0,maxLength) + "…";
|
|
}
|
|
|
|
return string;
|
|
},
|
|
|
|
/* titleTransform(title)
|
|
* Transforms the title of an event for usage.
|
|
* Replaces parts of the text as defined in config.titleReplace.
|
|
* Shortens title based on config.maxTitleLength
|
|
*
|
|
* argument title string - The title to transform.
|
|
*
|
|
* return string - The transformed title.
|
|
*/
|
|
titleTransform: function(title) {
|
|
for (var needle in this.config.titleReplace) {
|
|
var replacement = this.config.titleReplace[needle];
|
|
title = title.replace(needle, replacement);
|
|
}
|
|
|
|
title = this.shorten(title, this.config.maxTitleLength);
|
|
return title;
|
|
}
|
|
});
|