diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 00000000..136532c1
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,19 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 60
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 7
+# Issues with these labels will never be considered stale
+exemptLabels:
+ - pinned
+ - security
+ - under investigation
+ - pr welcome
+# Label to use when marking an issue as stale
+staleLabel: wontfix
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7942d2d..85565c83 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,9 +9,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [2.8.0] - Unreleased
-*This release is scheduled to be released on 2019-04-01.*
+*This release is scheduled to be released on 2019-07-01.*
### Added
+- Option to show event location in calendar
+- Finnish translation for "Feels" and "Weeks"
+- Russian translation for “Feels”
+- Calendar module: added `nextDaysRelative` config option
+- Add `broadcastPastEvents` config option for calendars to include events from the past `maximumNumberOfDays` in event broadcasts
+
Added UK Met Office Datapoint feed as a provider in the default weather module.
- added new provider class
- added suncalc.js dependency to calculate sun times (not provided in UK Met Office feed)
@@ -20,8 +26,17 @@ Added UK Met Office Datapoint feed as a provider in the default weather module.
- optionally display probability of precipitation (PoP) in current weather (UK Met Office data)
### Updated
+- English translation for "Feels" to "Feels like"
+- Fixed the example calender url in `config.js.sample`
+- Update `ical.js` to solve various calendar issues.
+- Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
+- Only update clock once per minute when seconds aren't shown
### Fixed
+- Allowance HTML5 autoplay-policy (policy is changed from Chrome 66 updates)
+- Handle SIGTERM messages
+- Fixes sliceMultiDayEvents so it respects maximumNumberOfDays
+>>>>>>> upstream/develop
## [2.7.1] - 2019-04-02
diff --git a/config/config.js.sample b/config/config.js.sample
index 6e419e59..8ec8622f 100644
--- a/config/config.js.sample
+++ b/config/config.js.sample
@@ -45,8 +45,7 @@ var config = {
calendars: [
{
symbol: "calendar-check",
- url: "webcal://www.calendarlabs.com/templates/ical/US-Holidays.ics"
- }
+ url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" }
]
}
},
@@ -59,7 +58,7 @@ var config = {
position: "top_right",
config: {
location: "New York",
- locationID: "", //ID from http://bulk.openweathermap.org/sample/; unzip the gz file and find your city
+ locationID: "", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
appid: "YOUR_OPENWEATHER_API_KEY"
}
},
@@ -69,7 +68,7 @@ var config = {
header: "Weather Forecast",
config: {
location: "New York",
- locationID: "5128581", //ID from https://openweathermap.org/city
+ locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
appid: "YOUR_OPENWEATHER_API_KEY"
}
},
diff --git a/js/app.js b/js/app.js
index 95c9693b..4a10ba6d 100644
--- a/js/app.js
+++ b/js/app.js
@@ -263,6 +263,15 @@ var App = function() {
this.stop();
process.exit(0);
});
+
+ /* We also need to listen to SIGTERM signals so we stop everything when we are asked to stop by the OS.
+ */
+ process.on("SIGTERM", () => {
+ console.log("[SIGTERM] Received. Shutting down server...");
+ setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds
+ this.stop();
+ process.exit(0);
+ });
};
module.exports = new App();
diff --git a/js/electron.js b/js/electron.js
index fc6ab98e..c5e9c4cb 100644
--- a/js/electron.js
+++ b/js/electron.js
@@ -17,6 +17,7 @@ const BrowserWindow = electron.BrowserWindow;
let mainWindow;
function createWindow() {
+ app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
var electronOptionsDefaults = {
width: 800,
height: 600,
diff --git a/modules/default/calendar/README.md b/modules/default/calendar/README.md
index b2bb5c7f..eb9324f2 100755
--- a/modules/default/calendar/README.md
+++ b/modules/default/calendar/README.md
@@ -30,6 +30,7 @@ The following properties can be configured:
| `maximumNumberOfDays` | The maximum number of days in the future.
**Default value:** `365`
| `displaySymbol` | Display a symbol in front of an entry.
**Possible values:** `true` or `false`
**Default value:** `true`
| `defaultSymbol` | The default symbol.
**Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website.
**Default value:** `calendar`
+| `showLocation` | Whether to show event locations.
**Possible values:** `true` or `false`
**Default value:** `false`
| `maxTitleLength` | The maximum title length.
**Possible values:** `10` - `50`
**Default value:** `25`
| `wrapEvents` | Wrap event titles to multiple lines. Breaks lines at the length defined by `maxTitleLength`.
**Possible values:** `true` or `false`
**Default value:** `false`
| `maxTitleLines` | The maximum number of lines a title will wrap vertically before being cut (Only enabled if `wrapEvents` is also enabled).
**Possible values:** `0` - `10`
**Default value:** `3`
@@ -53,8 +54,9 @@ The following properties can be configured:
| `hidePrivate` | Hides private calendar events.
**Possible values:** `true` or `false`
**Default value:** `false`
| `hideOngoing` | Hides calendar events that have already started.
**Possible values:** `true` or `false`
**Default value:** `false`
| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown.
Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.
**Required**
`filterBy` - string used to determine if filter is applied.
**Optional**
`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]
`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity
`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.
**Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}, {filterBy: '^[0-9]{1,}.*', regex: true}]`
**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`
-
+| `broadcastPastEvents` | If this is set to true, events from the past `maximumNumberOfDays` will be included in event broadcasts
**Default value:** `false`
+| `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`
+| `nextDaysRelative ` | If this is set to true, the appointments of today and tomorrow are displayed relatively, even if the timeformat is set to absolute.
**Default value:** `false`
### Calendar configuration
@@ -95,6 +97,7 @@ config: {
| `symbolClass` | Add a class to the cell of symbol.
| `titleClass` | Add a class to the title's cell.
| `timeClass` | Add a class to the time's cell.
+| `broadcastPastEvents` | Whether to include past events from this calendar. Overrides global setting
#### Calendar authentication options:
diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js
index d584a9ed..2eac086e 100755
--- a/modules/default/calendar/calendar.js
+++ b/modules/default/calendar/calendar.js
@@ -15,6 +15,7 @@ Module.register("calendar", {
maximumNumberOfDays: 365,
displaySymbol: true,
defaultSymbol: "calendar", // Fontawesome Symbol see http://fontawesome.io/cheatsheet/
+ showLocation: false,
displayRepeatingCountTitle: false,
defaultRepeatingCountTitle: "",
maxTitleLength: 25,
@@ -48,7 +49,9 @@ Module.register("calendar", {
},
broadcastEvents: true,
excludedEvents: [],
- sliceMultiDayEvents: false
+ sliceMultiDayEvents: false,
+ broadcastPastEvents: false,
+ nextDaysRelative: false
},
// Define required scripts.
@@ -82,7 +85,8 @@ Module.register("calendar", {
var calendarConfig = {
maximumEntries: calendar.maximumEntries,
- maximumNumberOfDays: calendar.maximumNumberOfDays
+ maximumNumberOfDays: calendar.maximumNumberOfDays,
+ broadcastPastEvents: calendar.broadcastPastEvents,
};
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = "";
@@ -325,7 +329,7 @@ Module.register("calendar", {
// If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else {
- if(this.config.timeFormat === "absolute") {
+ if(this.config.timeFormat === "absolute" && !this.config.nextDaysRelative) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
} else {
// Otherwise just say 'Today/Tomorrow at such-n-such time'
@@ -379,6 +383,31 @@ Module.register("calendar", {
currentFadeStep = e - startFade;
eventWrapper.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
}
+
+ if (this.config.showLocation) {
+ if (event.location !== false) {
+ var locationRow = document.createElement("tr");
+ locationRow.className = "normal xsmall light";
+
+ if (this.config.displaySymbol) {
+ var symbolCell = document.createElement("td");
+ locationRow.appendChild(symbolCell);
+ }
+
+ var descCell = document.createElement("td");
+ descCell.className = "location";
+ descCell.colSpan = "2";
+ descCell.innerHTML = event.location;
+ locationRow.appendChild(descCell);
+
+ wrapper.appendChild(locationRow);
+
+ if (e >= startFade) {
+ currentFadeStep = e - startFade;
+ locationRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
+ }
+ }
+ }
}
return wrapper;
@@ -436,10 +465,14 @@ Module.register("calendar", {
var events = [];
var today = moment().startOf("day");
var now = new Date();
+ var future = moment().startOf("day").add(this.config.maximumNumberOfDays, "days").toDate();
for (var c in this.calendarData) {
var calendar = this.calendarData[c];
for (var e in calendar) {
- var event = calendar[e];
+ var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
+ if(event.endDate < now) {
+ continue;
+ }
if(this.config.hidePrivate) {
if(event.class === "PRIVATE") {
// do not add the current event, skip it
@@ -460,24 +493,31 @@ Module.register("calendar", {
/* 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 maxCount = Math.ceil(((event.endDate - 1) - moment(event.startDate, "x").endOf("day").format("x"))/(1000*60*60*24)) + 1;
+ if (this.config.sliceMultiDayEvents && maxCount > 1) {
+ var splitEvents = [];
+ var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
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);
+ while (event.endDate > midnight) {
+ var thisEvent = JSON.parse(JSON.stringify(event)) // clone object
+ thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < (today + 24 * 60 * 60 * 1000);
+ thisEvent.endDate = midnight;
+ thisEvent.title += " (" + count + "/" + maxCount + ")";
+ splitEvents.push(thisEvent);
+
+ event.startDate = midnight;
+ count += 1;
+ midnight = moment(midnight, "x").add(1, "day").format("x"); // next day
+ }
+ // Last day
+ event.title += " ("+count+"/"+maxCount+")";
+ splitEvents.push(event);
+
+ for (event of splitEvents) {
+ if ((event.endDate > now) && (event.endDate <= future)) {
+ events.push(event);
+ }
+ }
} else {
events.push(event);
}
@@ -487,7 +527,6 @@ Module.register("calendar", {
events.sort(function (a, b) {
return a.startDate - b.startDate;
});
-
return events.slice(0, this.config.maximumEntries);
},
@@ -517,7 +556,8 @@ Module.register("calendar", {
symbolClass: calendarConfig.symbolClass,
titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass,
- auth: auth
+ auth: auth,
+ broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents,
});
},
diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js
index 0076a605..3a30aea2 100644
--- a/modules/default/calendar/calendarfetcher.js
+++ b/modules/default/calendar/calendarfetcher.js
@@ -8,7 +8,7 @@
var ical = require("./vendor/ical.js");
var moment = require("moment");
-var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) {
+var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) {
var self = this;
var reloadTimer = null;
@@ -74,6 +74,11 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var now = new Date();
var today = moment().startOf("day").toDate();
var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1,"seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
+ var past = today;
+
+ if (includePastEvents) {
+ past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate();
+ }
// FIXME:
// Ugly fix to solve the facebook birthday issue.
@@ -181,7 +186,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
rule.options.dtstart.setYear(1900);
}
- var dates = rule.between(today, future, true, limitFunction);
+ var dates = rule.between(past, future, true, limitFunction);
for (var d in dates) {
startDate = moment(new Date(dates[d]));
@@ -191,7 +196,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
continue;
}
- if (endDate.format("x") > now) {
+ if (includePastEvents || endDate.format("x") > now) {
newEvents.push({
title: title,
startDate: startDate.format("x"),
@@ -210,14 +215,21 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
// Single event.
var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event);
- if (!fullDayEvent && endDate < new Date()) {
- //console.log("It's not a fullday event, and it is in the past. So skip: " + title);
- continue;
- }
+ if (includePastEvents) {
+ if (endDate < past) {
+ //console.log("Past event is too far in the past. So skip: " + title);
+ continue;
+ }
+ } else {
+ if (!fullDayEvent && endDate < new Date()) {
+ //console.log("It's not a fullday event, and it is in the past. So skip: " + title);
+ continue;
+ }
- if (fullDayEvent && endDate <= today) {
- //console.log("It's a fullday event, and it is before today. So skip: " + title);
- continue;
+ if (fullDayEvent && endDate <= today) {
+ //console.log("It's a fullday event, and it is before today. So skip: " + title);
+ continue;
+ }
}
if (startDate > future) {
diff --git a/modules/default/calendar/debug.js b/modules/default/calendar/debug.js
index ddf0fb42..a568e1cd 100644
--- a/modules/default/calendar/debug.js
+++ b/modules/default/calendar/debug.js
@@ -15,6 +15,7 @@ var maximumEntries = 10;
var maximumNumberOfDays = 365;
var user = "magicmirror";
var pass = "MyStrongPass";
+var broadcastPastEvents = false;
var auth = {
user: user,
diff --git a/modules/default/calendar/node_helper.js b/modules/default/calendar/node_helper.js
index 25e7f1f7..9c6e4979 100644
--- a/modules/default/calendar/node_helper.js
+++ b/modules/default/calendar/node_helper.js
@@ -24,7 +24,7 @@ module.exports = NodeHelper.create({
socketNotificationReceived: function(notification, payload) {
if (notification === "ADD_CALENDAR") {
//console.log('ADD_CALENDAR: ');
- this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth);
+ this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents);
}
},
@@ -36,7 +36,7 @@ module.exports = NodeHelper.create({
* attribute reloadInterval number - Reload interval in milliseconds.
*/
- createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) {
+ createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents) {
var self = this;
if (!validUrl.isUri(url)) {
@@ -47,7 +47,7 @@ module.exports = NodeHelper.create({
var fetcher;
if (typeof self.fetchers[url] === "undefined") {
console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
- fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth);
+ fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents);
fetcher.onReceive(function(fetcher) {
//console.log('Broadcast events.');
diff --git a/modules/default/calendar/vendor/ical.js/ical.js b/modules/default/calendar/vendor/ical.js/ical.js
index 8f0c532b..024625b7 100644
--- a/modules/default/calendar/vendor/ical.js/ical.js
+++ b/modules/default/calendar/vendor/ical.js/ical.js
@@ -33,9 +33,9 @@
for (var i = 0; i