Merge remote-tracking branch 'upstream/master'

Update MagicMirror
This commit is contained in:
Thierry Nischelwitzer
2019-04-10 18:07:55 +02:00
46 changed files with 1301 additions and 599 deletions

View File

@@ -32,6 +32,7 @@ The following properties can be configured:
| `defaultSymbol` | The default symbol. <br><br> **Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website. <br> **Default value:** `calendar`
| `maxTitleLength` | The maximum title length. <br><br> **Possible values:** `10` - `50` <br> **Default value:** `25`
| `wrapEvents` | Wrap event titles to multiple lines. Breaks lines at the length defined by `maxTitleLength`. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `maxTitleLines` | The maximum number of lines a title will wrap vertically before being cut (Only enabled if `wrapEvents` is also enabled). <br><br> **Possible values:** `0` - `10` <br> **Default value:** `3`
| `fetchInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `300000` (5 minutes)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:** `0` - `5000` <br> **Default value:** `2000` (2 seconds)
| `fade` | Fade the future events to black. (Gradient) <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
@@ -52,6 +53,8 @@ The following properties can be configured:
| `hidePrivate` | Hides private calendar events. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `hideOngoing` | Hides calendar events that have already started. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown. <br><br>Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.<br>**Required**<br>`filterBy` - string used to determine if filter is applied.<br>**Optional**<br>`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]<br>`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity<br>`regex` - set to `true` if filterBy is a regex. For those not familiar with regex it is used for pattern matching, please see [here](https://regexr.com/) for more info.<br><br> **Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}, {filterBy: '^[0-9]{1,}.*', regex: true}]` <br> **Default value:** `[]`
| `sliceMultiDayEvents` | If this is set to true, events exceeding at least one midnight will be sliced into separate events including a counter like (1/2). This is especially helpful in "dateheaders" mode. Events will be sliced at midnight, end time for all events but the last will be 23:59 **Default value:** `true`
### Calendar configuration
@@ -87,6 +90,7 @@ config: {
| `repeatingCountTitle` | The count title for yearly repating events in this calendar. <br><br> **Example:** `'Birthday'`
| `maximumEntries` | The maximum number of events shown. Overrides global setting. **Possible values:** `0` - `100`
| `maximumNumberOfDays` | The maximum number of days in the future. Overrides global setting
| `name` | The name of the calendar. Included in event broadcasts as `calendarName`.
| `auth` | The object containing options for authentication against the calendar.
| `symbolClass` | Add a class to the cell of symbol.
| `titleClass` | Add a class to the title's cell.

View File

@@ -19,13 +19,14 @@ Module.register("calendar", {
defaultRepeatingCountTitle: "",
maxTitleLength: 25,
wrapEvents: false, // wrap events to multiple lines breaking at maxTitleLength
maxTitleLines: 3,
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
animationSpeed: 2000,
fade: true,
urgency: 7,
timeFormat: "relative",
dateFormat: "MMM Do",
dateEndFormat: "HH:mm",
dateEndFormat: "LT",
fullDayEventDateFormat: "MMM Do",
showEnd: false,
getRelative: 6,
@@ -46,12 +47,13 @@ Module.register("calendar", {
"'s birthday": ""
},
broadcastEvents: true,
excludedEvents: []
excludedEvents: [],
sliceMultiDayEvents: false
},
// Define required scripts.
getStyles: function () {
return ["calendar.css", "font-awesome5.css", "font-awesome5.v4shims.css"];
return ["calendar.css", "font-awesome.css"];
},
// Define required scripts.
@@ -103,6 +105,13 @@ Module.register("calendar", {
}
this.addCalendar(calendar.url, calendar.auth, calendarConfig);
// Trigger ADD_CALENDAR every fetchInterval to make sure there is always a calendar
// fetcher running on the server side.
var self = this;
setInterval(function() {
self.addCalendar(calendar.url, calendar.auth, calendarConfig);
}, self.config.fetchInterval);
}
this.calendarData = {};
@@ -220,7 +229,7 @@ Module.register("calendar", {
var titleWrapper = document.createElement("td"),
repeatingCountTitle = "";
if (this.config.displayRepeatingCountTitle) {
if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) {
repeatingCountTitle = this.countTitleForUrl(event.url);
@@ -296,12 +305,12 @@ Module.register("calendar", {
if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
// This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
}
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
}
}
if(this.config.showEnd){
@@ -447,7 +456,31 @@ Module.register("calendar", {
}
event.url = c;
event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000);
events.push(event);
/* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
*/
if (this.config.sliceMultiDayEvents) {
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x"); //next midnight
var count = 1;
var maxCount = Math.ceil(((event.endDate - 1) - moment(event.startDate, "x").endOf("day").format("x"))/(1000*60*60*24)) + 1
if (event.endDate > midnight) {
while (event.endDate > midnight) {
var nextEvent = JSON.parse(JSON.stringify(event)); //make a copy without reference to the original event
nextEvent.startDate = midnight;
event.endDate = midnight;
event.title += " (" + count + "/" + maxCount + ")";
events.push(event);
event = nextEvent;
count += 1;
midnight = moment(midnight, "x").add(1, "day").format("x"); //move further one day for next split
}
event.title += " ("+count+"/"+maxCount+")";
}
events.push(event);
} else {
events.push(event);
}
}
}
@@ -536,6 +569,17 @@ Module.register("calendar", {
return this.getCalendarProperty(url, "timeClass", "");
},
/* calendarNameForUrl(url)
* Retrieves the calendar name for a specific url.
*
* argument url string - Url to look for.
*
* return string - The name of the calendar
*/
calendarNameForUrl: function (url) {
return this.getCalendarProperty(url, "name", "");
},
/* colorForUrl(url)
* Retrieves the color for a specific url.
*
@@ -584,9 +628,10 @@ Module.register("calendar", {
* @param {string} string Text string to shorten
* @param {number} maxLength The max length of the string
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
* @param {number} maxTitleLines The max number of vertical lines before cutting event title
* @returns {string} The shortened string
*/
shorten: function (string, maxLength, wrapEvents) {
shorten: function (string, maxLength, wrapEvents, maxTitleLines) {
if (typeof string !== "string") {
return "";
}
@@ -595,12 +640,21 @@ Module.register("calendar", {
var temp = "";
var currentLine = "";
var words = string.split(" ");
var line = 0;
for (var i = 0; i < words.length; i++) {
var word = words[i];
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) { // max - 1 to account for a space
currentLine += (word + " ");
} else {
line++;
if (line > maxTitleLines - 1) {
if (i < words.length) {
currentLine += "&hellip;";
}
break;
}
if (currentLine.length > 0) {
temp += (currentLine + "<br>" + word + " ");
} else {
@@ -651,7 +705,7 @@ Module.register("calendar", {
title = title.replace(needle, replacement);
}
title = this.shorten(title, this.config.maxTitleLength, this.config.wrapEvents);
title = this.shorten(title, this.config.maxTitleLength, this.config.wrapEvents, this.config.maxTitleLines);
return title;
},
@@ -666,6 +720,7 @@ Module.register("calendar", {
for (var e in calendar) {
var event = cloneObject(calendar[e]);
event.symbol = this.symbolsForUrl(url);
event.calendarName = this.calendarNameForUrl(url);
event.color = this.colorForUrl(url);
delete event.url;
eventList.push(event);

View File

@@ -273,7 +273,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
/* isFullDayEvent(event)
* Checks if an event is a fullday event.
*
* argument event obejct - The event object to check.
* argument event object - The event object to check.
*
* return bool - The event is a fullday event.
*/

View File

@@ -80,16 +80,45 @@
}
}
var addTZ = function(dt, name, params){
var addTZ = function(dt, params){
var p = parseParams(params);
if (params && p){
dt[name].tz = p.TZID
if (params && p && dt){
dt.tz = p.TZID
}
return dt
}
var parseTimestamp = function(val){
//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
return new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
return new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
}
return undefined;
}
var dateParam = function(name){
return function(val, params, curr){
@@ -108,37 +137,24 @@
comps[3]
);
return addTZ(curr, name, params);
curr[name] = addTZ(curr[name], params);
return curr;
}
}
curr[name] = []
val.split(',').forEach(function(val){
var newDate = parseTimestamp(val);
curr[name].push(addTZ(newDate, params));
});
//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
curr[name] = new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
curr[name] = new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
if (curr[name].length === 0){
delete curr[name];
} else if (curr[name].length === 1){
curr[name] = curr[name][0];
}
return addTZ(curr, name, params)
return curr;
}
}
@@ -148,7 +164,11 @@
if (date.exdates === undefined) {
date.exdates = [];
}
date.exdates.push(date.exdate);
if (Array.isArray(date.exdate)){
date.exdates = date.exdates.concat(date.exdate);
} else {
date.exdates.push(date.exdate);
}
return date;
}
}