diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e3ececd6..c09454eb 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -6,6 +6,8 @@ If you're not sure if it's a real bug or if it's just you, please open a topic o Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting) +A common problem is that your config file could be invalid. Please run in your MagicMirror directory: `npm run config:check` and see if it reports an error. + ## I found a bug in the MagicMirror installer If you are facing an issue or found a bug while trying to install MagicMirror via the installer please report it in the respective GitHub repository: @@ -23,9 +25,9 @@ If you are facing an issue or found a bug while running MagicMirror inside a Doc Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line. When submitting a new issue, please supply the following information: -**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX). +**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX). -**Node Version**: Make sure it's version 8 or later. +**Node Version**: Make sure it's version 10 or later. **MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file. diff --git a/.github/workflows/codecov-test-suites.yml b/.github/workflows/codecov-test-suites.yml new file mode 100644 index 00000000..0d98e3a0 --- /dev/null +++ b/.github/workflows/codecov-test-suites.yml @@ -0,0 +1,24 @@ +# This workflow runs the automated test and uploads the coverage results to codecov.io + +name: "Run Codecov Tests" + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop ] + +jobs: + run-and-upload-coverage-report: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: | + Xvfb :99 -screen 0 1024x768x16 & + export DISPLAY=:99 + npm ci + npm run test:coverage + - uses: codecov/codecov-action@v1 + with: + file: ./coverage/lcov.info + fail_ci_if_error: true diff --git a/.github/workflows/enforce-changelog.yml b/.github/workflows/enforce-changelog.yml index eefa1645..67a95a48 100644 --- a/.github/workflows/enforce-changelog.yml +++ b/.github/workflows/enforce-changelog.yml @@ -1,10 +1,12 @@ +# This workflow enforces the update of a changelog file on every pull request + name: "Enforce Changelog" + on: pull_request: types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] jobs: - # Enforces the update of a changelog file on every pull request check: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/node-ci.js.yml b/.github/workflows/node-ci.js.yml index f794c941..7f6f23b1 100644 --- a/.github/workflows/node-ci.js.yml +++ b/.github/workflows/node-ci.js.yml @@ -1,7 +1,7 @@ # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions -name: Automated Tests +name: "Run Automated Tests" on: push: @@ -11,13 +11,10 @@ on: jobs: test: - runs-on: ubuntu-latest - strategy: matrix: node-version: [10.x, 12.x, 14.x] - steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 50270e71..835cc1f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,54 @@ This project adheres to [Semantic Versioning](https://semver.org/). ❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror² -## [2.14.1] - 2021-03-07 +## [2.15.0] - Unreleased (Develop Branch) + +_This release is scheduled to be released on 2021-04-01._ ### Added -- @MystaraTheGreat added hiddenOnStartup flag to module config (#2475) +- Added GitHub workflows for automated testing and changelog enforcement. +- Added CodeCov badge to Readme. +- Added CURRENTWEATHER_TYPE notification to currentweather and weather module, use it in compliments module. +- Added `start:dev` command to the npm scripts for starting electron with devTools open. +- Added logging when using deprecated modules weatherforecast or currentweather. +- Portuguese translations for "MODULE_CONFIG_CHANGED" and PRECIP. +- Respect parameter ColoredSymbolOnly also for custom events +- Added a new parameter to hide time portion on relative times +- `module.show` has now the option for a callback on error. +- Added locale to sample config file +- Added support for self-signed certificates for the default calendar module (#466) +- Added hiddenOnStartup flag to module config (#2475) + +### Updated + +- Updated markdown files. +- Cleaned up old code on server side. +- Convert `-0` to `0` when displaying temperature. +- Code cleanup for FEELS like and added {DEGREE} placeholder for FEELSLIKE for each language +- Converted newsfeed module to use templates. +- Update documentation and help screen about invalid config files. +- Moving weather provider specific code and configuration into each provider and making hourly part of the interface. +- Bump electron to v11 and enable contextIsolation. +- Dont update the DOM when a module is not displayed. +- Cleaned up jsdoc and tests. +- Exposed logger as node module for easier access for 3rd party modules + +### Removed + +- Removed danger.js library. + +### Fixed + +- Added default log levels to stop calendar log spamming. +- Fix socket.io cors errors, see [breaking change since socket.io v3](https://socket.io/docs/v3/handling-cors/) +- Fix Issue with weather forecast icons due to fixed day start and end time (#2221) +- Fix empty directory for each module's main javascript file in the inspector +- Fix Issue with weather forecast icons unit tests with different timezones (#2221) +- Fix issue with unencoded characters in translated strings when using nunjuck template (`Loading …` as an example) +- Fix socket.io backward compatibility with socket v2 clients +- 3rd party module language loading if language is English +- Fix e2e tests after spectron update ## [2.14.0] - 2021-01-01 @@ -31,7 +74,6 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank, - Calendar: new options "limitDays" and "coloredEvents". - Added new option "limitDays" - limit the number of discreet days displayed. - Added new option "customEvents" - use custom symbol/color based on keyword in event title. -- Added GitHub workflows for automated testing and changelog enforcement. ### Updated diff --git a/LICENSE.md b/LICENSE.md index b766bd7a..c511b4cb 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # The MIT License (MIT) -Copyright © 2016-2020 Michael Teeuw +Copyright © 2016-2021 Michael Teeuw Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index d4ca67ab..cf534d5b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@  -
+
@@ -38,7 +39,6 @@ If we receive enough donations we might even be able to free up some working hou To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link. -
-
+
npm run config:check" } }, { diff --git a/js/deprecated.js b/js/deprecated.js index 4f56b41d..6bf4a626 100644 --- a/js/deprecated.js +++ b/js/deprecated.js @@ -6,11 +6,6 @@ * Olex S. original idea this deprecated option */ -var deprecated = { +module.exports = { configs: ["kioskmode"] }; - -/*************** DO NOT EDIT THE LINE BELOW ***************/ -if (typeof module !== "undefined") { - module.exports = deprecated; -} diff --git a/js/electron.js b/js/electron.js index 216dee48..6c2f44a7 100644 --- a/js/electron.js +++ b/js/electron.js @@ -2,10 +2,10 @@ const electron = require("electron"); const core = require("./app.js"); -const Log = require("./logger.js"); +const Log = require("logger"); // Config -var config = process.env.config ? JSON.parse(process.env.config) : {}; +let config = process.env.config ? JSON.parse(process.env.config) : {}; // Module to control application life. const app = electron.app; // Module to create native browser window. @@ -20,13 +20,14 @@ let mainWindow; */ function createWindow() { app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); - var electronOptionsDefaults = { + let electronOptionsDefaults = { width: 800, height: 600, x: 0, y: 0, darkTheme: true, webPreferences: { + contextIsolation: true, nodeIntegration: false, zoomFactor: config.zoom }, @@ -42,7 +43,7 @@ function createWindow() { electronOptionsDefaults.autoHideMenuBar = true; } - var electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions); + const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions); // Create the browser window. mainWindow = new BrowserWindow(electronOptions); @@ -50,14 +51,14 @@ function createWindow() { // and load the index.html of the app. // If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost - var prefix; + let prefix; if (config["tls"] !== null && config["tls"]) { prefix = "https://"; } else { prefix = "http://"; } - var address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address; + let address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address; mainWindow.loadURL(`${prefix}${address}:${config.port}`); // Open the DevTools if run with "npm start dev" @@ -125,7 +126,7 @@ app.on("before-quit", (event) => { // Start the core application if server is run on localhost // This starts all node helpers and starts the webserver. -if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) > -1) { +if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(config.address)) { core.start(function (c) { config = c; }); diff --git a/js/loader.js b/js/loader.js index a5e45b4e..48e74f71 100644 --- a/js/loader.js +++ b/js/loader.js @@ -125,7 +125,7 @@ var Loader = (function () { * @param {Function} callback Function called when done. */ var loadModule = function (module, callback) { - var url = module.path + "/" + module.file; + var url = module.path + module.file; var afterLoad = function () { var moduleObject = Module.create(module.name); diff --git a/js/main.js b/js/main.js index b7138d99..6f5d9484 100644 --- a/js/main.js +++ b/js/main.js @@ -295,6 +295,9 @@ var MM = (function () { // Otherwise cancel show action. if (module.lockStrings.length !== 0 && options.force !== true) { Log.log("Will not show " + module.name + ". LockStrings active: " + module.lockStrings.join(",")); + if (typeof options.onError === "function") { + options.onError(new Error("LOCK_STRING_ACTIVE")); + } return; } @@ -440,7 +443,6 @@ var MM = (function () { * Removes a module instance from the collection. * * @param {object} module The module instance to remove from the collection. - * * @returns {Module[]} Filtered collection of modules. */ var exceptModule = function (module) { @@ -547,6 +549,11 @@ var MM = (function () { return; } + if (!module.data.position) { + Log.warn("module tries to update the DOM without being displayed."); + return; + } + // Further implementation is done in the private method. updateDom(module, speed); }, diff --git a/js/module.js b/js/module.js index 663413b6..042b674b 100644 --- a/js/module.js +++ b/js/module.js @@ -176,7 +176,7 @@ var Module = Class.extend({ }); this._nunjucksEnvironment.addFilter("translate", function (str, variables) { - return self.translate(str, variables); + return nunjucks.runtime.markSafe(self.translate(str, variables)); }); return this._nunjucksEnvironment; @@ -311,33 +311,33 @@ var Module = Class.extend({ * * @param {Function} callback Function called when done. */ - loadTranslations: function (callback) { - var self = this; - var translations = this.getTranslations(); - var lang = config.language.toLowerCase(); + loadTranslations(callback) { + const translations = this.getTranslations() || {}; + const language = config.language.toLowerCase(); - // The variable `first` will contain the first - // defined translation after the following line. - for (var first in translations) { - break; - } + const languages = Object.keys(translations); + const fallbackLanguage = languages[0]; - if (translations) { - var translationFile = translations[lang] || undefined; - var translationsFallbackFile = translations[first]; - - // If a translation file is set, load it and then also load the fallback translation file. - // Otherwise only load the fallback translation file. - if (translationFile !== undefined && translationFile !== translationsFallbackFile) { - Translator.load(self, translationFile, false, function () { - Translator.load(self, translationsFallbackFile, true, callback); - }); - } else { - Translator.load(self, translationsFallbackFile, true, callback); - } - } else { + if (languages.length === 0) { callback(); + return; } + + const translationFile = translations[language]; + const translationsFallbackFile = translations[fallbackLanguage]; + + if (!translationFile) { + Translator.load(this, translationsFallbackFile, true, callback); + return; + } + + Translator.load(this, translationFile, false, () => { + if (translationFile !== translationsFallbackFile) { + Translator.load(this, translationsFallbackFile, true, callback); + } else { + callback(); + } + }); }, /** @@ -428,12 +428,11 @@ var Module = Class.extend({ callback = callback || function () {}; options = options || {}; - var self = this; MM.showModule( this, speed, - function () { - self.resume(); + () => { + this.resume(); callback(); }, options diff --git a/js/node_helper.js b/js/node_helper.js index 4a31fdee..81d2d9d5 100644 --- a/js/node_helper.js +++ b/js/node_helper.js @@ -5,21 +5,21 @@ * MIT Licensed. */ const Class = require("./class.js"); -const Log = require("./logger.js"); +const Log = require("logger"); const express = require("express"); -var NodeHelper = Class.extend({ - init: function () { +const NodeHelper = Class.extend({ + init() { Log.log("Initializing new module helper ..."); }, - loaded: function (callback) { - Log.log("Module helper loaded: " + this.name); + loaded(callback) { + Log.log(`Module helper loaded: ${this.name}`); callback(); }, - start: function () { - Log.log("Starting module helper: " + this.name); + start() { + Log.log(`Starting module helper: ${this.name}`); }, /* stop() @@ -28,8 +28,8 @@ var NodeHelper = Class.extend({ * gracefully exit the module. * */ - stop: function () { - Log.log("Stopping module helper: " + this.name); + stop() { + Log.log(`Stopping module helper: ${this.name}`); }, /* socketNotificationReceived(notification, payload) @@ -38,8 +38,8 @@ var NodeHelper = Class.extend({ * argument notification string - The identifier of the notification. * argument payload mixed - The payload of the notification. */ - socketNotificationReceived: function (notification, payload) { - Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload); + socketNotificationReceived(notification, payload) { + Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`); }, /* setName(name) @@ -47,7 +47,7 @@ var NodeHelper = Class.extend({ * * argument name string - Module name. */ - setName: function (name) { + setName(name) { this.name = name; }, @@ -56,7 +56,7 @@ var NodeHelper = Class.extend({ * * argument path string - Module path. */ - setPath: function (path) { + setPath(path) { this.path = path; }, @@ -66,7 +66,7 @@ var NodeHelper = Class.extend({ * argument notification string - The identifier of the notification. * argument payload mixed - The payload of the notification. */ - sendSocketNotification: function (notification, payload) { + sendSocketNotification(notification, payload) { this.io.of(this.name).emit(notification, payload); }, @@ -76,11 +76,10 @@ var NodeHelper = Class.extend({ * * argument app Express app - The Express app object. */ - setExpressApp: function (app) { + setExpressApp(app) { this.expressApp = app; - var publicPath = this.path + "/public"; - app.use("/" + this.name, express.static(publicPath)); + app.use(`/${this.name}`, express.static(`${this.path}/public`)); }, /* setSocketIO(io) @@ -89,27 +88,25 @@ var NodeHelper = Class.extend({ * * argument io Socket.io - The Socket io object. */ - setSocketIO: function (io) { - var self = this; - self.io = io; + setSocketIO(io) { + this.io = io; - Log.log("Connecting socket for: " + this.name); - var namespace = this.name; - io.of(namespace).on("connection", function (socket) { + Log.log(`Connecting socket for: ${this.name}`); + + io.of(this.name).on("connection", (socket) => { // add a catch all event. - var onevent = socket.onevent; + const onevent = socket.onevent; socket.onevent = function (packet) { - var args = packet.data || []; + const args = packet.data || []; onevent.call(this, packet); // original call packet.data = ["*"].concat(args); onevent.call(this, packet); // additional call to catch-all }; // register catch all. - socket.on("*", function (notification, payload) { + socket.on("*", (notification, payload) => { if (notification !== "*") { - //Log.log('received message in namespace: ' + namespace); - self.socketNotificationReceived(notification, payload); + this.socketNotificationReceived(notification, payload); } }); }); @@ -120,7 +117,4 @@ NodeHelper.create = function (moduleDefinition) { return NodeHelper.extend(moduleDefinition); }; -/*************** DO NOT EDIT THE LINE BELOW ***************/ -if (typeof module !== "undefined") { - module.exports = NodeHelper; -} +module.exports = NodeHelper; diff --git a/js/server.js b/js/server.js index cd26190b..5ce9435d 100644 --- a/js/server.js +++ b/js/server.js @@ -4,25 +4,29 @@ * By Michael Teeuw https://michaelteeuw.nl * MIT Licensed. */ -var express = require("express"); -var app = require("express")(); -var path = require("path"); -var ipfilter = require("express-ipfilter").IpFilter; -var fs = require("fs"); -var helmet = require("helmet"); +const express = require("express"); +const app = require("express")(); +const path = require("path"); +const ipfilter = require("express-ipfilter").IpFilter; +const fs = require("fs"); +const helmet = require("helmet"); -var Log = require("./logger.js"); -var Utils = require("./utils.js"); +const Log = require("logger"); +const Utils = require("./utils.js"); -var Server = function (config, callback) { - var port = config.port; - if (process.env.MM_PORT) { - port = process.env.MM_PORT; - } +/** + * Server + * + * @param {object} config The MM config + * @param {Function} callback Function called when done. + * @class + */ +function Server(config, callback) { + const port = process.env.MM_PORT || config.port; - var server = null; + let server = null; if (config.useHttps) { - var options = { + const options = { key: fs.readFileSync(config.httpsPrivateKey), cert: fs.readFileSync(config.httpsCertificate) }; @@ -30,18 +34,24 @@ var Server = function (config, callback) { } else { server = require("http").Server(app); } - var io = require("socket.io")(server); + const io = require("socket.io")(server, { + cors: { + origin: /.*$/, + credentials: true + }, + allowEIO3: true + }); - Log.log("Starting server on port " + port + " ... "); + Log.log(`Starting server on port ${port} ... `); - server.listen(port, config.address ? config.address : "localhost"); + server.listen(port, config.address || "localhost"); if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) { Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs")); } app.use(function (req, res, next) { - var result = ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) { + ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) { if (err === undefined) { return next(); } @@ -52,10 +62,9 @@ var Server = function (config, callback) { app.use(helmet({ contentSecurityPolicy: false })); app.use("/js", express.static(__dirname)); - var directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"]; - var directory; - for (var i in directories) { - directory = directories[i]; + + const directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"]; + for (const directory of directories) { app.use(directory, express.static(path.resolve(global.root_path + directory))); } @@ -68,10 +77,10 @@ var Server = function (config, callback) { }); app.get("/", function (req, res) { - var html = fs.readFileSync(path.resolve(global.root_path + "/index.html"), { encoding: "utf8" }); + let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" }); html = html.replace("#VERSION#", global.version); - var configFile = "config/config.js"; + let configFile = "config/config.js"; if (typeof global.configuration_file !== "undefined") { configFile = global.configuration_file; } @@ -83,6 +92,6 @@ var Server = function (config, callback) { if (typeof callback === "function") { callback(app, io); } -}; +} module.exports = Server; diff --git a/js/translator.js b/js/translator.js index 82eec53a..841adb66 100644 --- a/js/translator.js +++ b/js/translator.js @@ -103,26 +103,19 @@ var Translator = (function () { * @param {boolean} isFallback Flag to indicate fallback translations. * @param {Function} callback Function called when done. */ - load: function (module, file, isFallback, callback) { - if (!isFallback) { - Log.log(module.name + " - Load translation: " + file); - } else { - Log.log(module.name + " - Load translation fallback: " + file); + load(module, file, isFallback, callback) { + Log.log(`${module.name} - Load translation${isFallback && " fallback"}: ${file}`); + + if (this.translationsFallback[module.name]) { + callback(); + return; } - var self = this; - if (!this.translationsFallback[module.name]) { - loadJSON(module.file(file), function (json) { - if (!isFallback) { - self.translations[module.name] = json; - } else { - self.translationsFallback[module.name] = json; - } - callback(); - }); - } else { + loadJSON(module.file(file), (json) => { + const property = isFallback ? "translationsFallback" : "translations"; + this[property][module.name] = json; callback(); - } + }); }, /** diff --git a/js/utils.js b/js/utils.js index 5044447d..1043ae3e 100644 --- a/js/utils.js +++ b/js/utils.js @@ -4,9 +4,9 @@ * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * MIT Licensed. */ -var colors = require("colors/safe"); +const colors = require("colors/safe"); -var Utils = { +module.exports = { colors: { warn: colors.yellow, error: colors.red, @@ -14,7 +14,3 @@ var Utils = { pass: colors.green } }; - -if (typeof module !== "undefined") { - module.exports = Utils; -} diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js index 73af9ecd..2cce00c7 100755 --- a/modules/default/calendar/calendar.js +++ b/modules/default/calendar/calendar.js @@ -36,6 +36,7 @@ Module.register("calendar", { fadePoint: 0.25, // Start on 1/4th of the list. hidePrivate: false, hideOngoing: false, + hideTime: false, colored: false, coloredSymbolOnly: false, customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched @@ -57,7 +58,8 @@ Module.register("calendar", { excludedEvents: [], sliceMultiDayEvents: false, broadcastPastEvents: false, - nextDaysRelative: false + nextDaysRelative: false, + selfSignedCert: false }, requiresVersion: "2.1.0", @@ -100,7 +102,8 @@ Module.register("calendar", { var calendarConfig = { maximumEntries: calendar.maximumEntries, maximumNumberOfDays: calendar.maximumNumberOfDays, - broadcastPastEvents: calendar.broadcastPastEvents + broadcastPastEvents: calendar.broadcastPastEvents, + selfSignedCert: calendar.selfSignedCert }; if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) { calendarConfig.symbolClass = ""; @@ -277,8 +280,11 @@ Module.register("calendar", { if (typeof this.config.customEvents[ev].color !== "undefined" && this.config.customEvents[ev].color !== "") { needle = new RegExp(this.config.customEvents[ev].keyword, "gi"); if (needle.test(event.title)) { - eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color; - titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color; + // Respect parameter ColoredSymbolOnly also for custom events + if (!this.config.coloredSymbolOnly) { + eventWrapper.style.cssText = "color:" + this.config.customEvents[ev].color; + titleWrapper.style.cssText = "color:" + this.config.customEvents[ev].color; + } if (this.config.displaySymbol) { symbolWrapper.style.cssText = "color:" + this.config.customEvents[ev].color; } @@ -363,7 +369,17 @@ Module.register("calendar", { // Show relative times if (event.startDate >= now) { // Use relative time - timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar()); + if (!this.config.hideTime) { + timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar()); + } else { + timeWrapper.innerHTML = this.capFirst( + moment(event.startDate, "x").calendar(null, { + sameDay: "[" + this.translate("TODAY") + "]", + nextDay: "[" + this.translate("TOMORROW") + "]", + nextWeek: "dddd" + }) + ); + } if (event.startDate - now < this.config.getRelative * oneHour) { // If event is within getRelative hours, display 'in xxx' time format or moment.fromNow() timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow()); @@ -592,7 +608,8 @@ Module.register("calendar", { titleClass: calendarConfig.titleClass, timeClass: calendarConfig.timeClass, auth: auth, - broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents + broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents, + selfSignedCert: calendarConfig.selfSignedCert || this.config.selfSignedCert }); }, diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index 6c268c46..73f92b1e 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -4,7 +4,7 @@ * By Michael Teeuw https://michaelteeuw.nl * MIT Licensed. */ -const Log = require("../../../js/logger.js"); +const Log = require("logger"); const ical = require("node-ical"); const request = require("request"); @@ -25,9 +25,10 @@ const moment = require("moment"); * @param {number} maximumNumberOfDays The maximum number of days an event should be in the future. * @param {object} auth The object containing options for authentication against the calendar. * @param {boolean} includePastEvents If true events from the past maximumNumberOfDays will be fetched too + * @param {boolean} selfSignedCert If true, the server certificate is not verified against the list of supplied CAs. * @class */ -const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) { +const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents, selfSignedCert) { const self = this; let reloadTimer = null; @@ -51,6 +52,13 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn gzip: true }; + if (selfSignedCert) { + var agentOptions = { + rejectUnauthorized: false + }; + opts.agentOptions = agentOptions; + } + if (auth) { if (auth.method === "bearer") { opts.auth = { diff --git a/modules/default/calendar/node_helper.js b/modules/default/calendar/node_helper.js index 06fa28ec..e1813737 100644 --- a/modules/default/calendar/node_helper.js +++ b/modules/default/calendar/node_helper.js @@ -7,7 +7,7 @@ const NodeHelper = require("node_helper"); const validUrl = require("valid-url"); const CalendarFetcher = require("./calendarfetcher.js"); -const Log = require("../../../js/logger"); +const Log = require("logger"); module.exports = NodeHelper.create({ // Override start method. @@ -19,7 +19,7 @@ module.exports = NodeHelper.create({ // Override socketNotificationReceived method. socketNotificationReceived: function (notification, payload) { if (notification === "ADD_CALENDAR") { - this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.id); + this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.selfSignedCert, payload.id); } }, @@ -34,9 +34,10 @@ module.exports = NodeHelper.create({ * @param {number} maximumNumberOfDays The maximum number of days an event should be in the future. * @param {object} auth The object containing options for authentication against the calendar. * @param {boolean} broadcastPastEvents If true events from the past maximumNumberOfDays will be included in event broadcasts + * @param {boolean} selfSignedCert If true, the server certificate is not verified against the list of supplied CAs. * @param {string} identifier ID of the module */ - createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, identifier) { + createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert, identifier) { var self = this; if (!validUrl.isUri(url)) { @@ -47,7 +48,7 @@ module.exports = NodeHelper.create({ var fetcher; if (typeof self.fetchers[identifier + url] === "undefined") { Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval); - fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents); + fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert); fetcher.onReceive(function (fetcher) { self.sendSocketNotification("CALENDAR_EVENTS", { diff --git a/modules/default/compliments/compliments.js b/modules/default/compliments/compliments.js index 412fe170..6613a2c8 100644 --- a/modules/default/compliments/compliments.js +++ b/modules/default/compliments/compliments.js @@ -182,34 +182,14 @@ Module.register("compliments", { }, // From data currentweather set weather type - setCurrentWeatherType: function (data) { - var weatherIconTable = { - "01d": "day_sunny", - "02d": "day_cloudy", - "03d": "cloudy", - "04d": "cloudy_windy", - "09d": "showers", - "10d": "rain", - "11d": "thunderstorm", - "13d": "snow", - "50d": "fog", - "01n": "night_clear", - "02n": "night_cloudy", - "03n": "night_cloudy", - "04n": "night_cloudy", - "09n": "night_showers", - "10n": "night_rain", - "11n": "night_thunderstorm", - "13n": "night_snow", - "50n": "night_alt_cloudy_windy" - }; - this.currentWeatherType = weatherIconTable[data.weather[0].icon]; + setCurrentWeatherType: function (type) { + this.currentWeatherType = type; }, // Override notification handler. notificationReceived: function (notification, payload, sender) { - if (notification === "CURRENTWEATHER_DATA") { - this.setCurrentWeatherType(payload.data); + if (notification === "CURRENTWEATHER_TYPE") { + this.setCurrentWeatherType(payload.type); } } }); diff --git a/modules/default/currentweather/README.md b/modules/default/currentweather/README.md index d4db59ea..d2df6d2a 100644 --- a/modules/default/currentweather/README.md +++ b/modules/default/currentweather/README.md @@ -1,5 +1,7 @@ # Module: Current Weather +> :warning: **This module is deprecated in favor of the [weather](https://docs.magicmirror.builders/modules/weather.html) module.** + The `currentweather` module is one of the default modules of the MagicMirror. This module displays the current weather, including the windspeed, the sunset or sunrise time, the temperature and an icon to display the current conditions. diff --git a/modules/default/currentweather/currentweather.js b/modules/default/currentweather/currentweather.js index 918ed460..0d22b40a 100644 --- a/modules/default/currentweather/currentweather.js +++ b/modules/default/currentweather/currentweather.js @@ -3,6 +3,8 @@ * * By Michael Teeuw https://michaelteeuw.nl * MIT Licensed. + * + * This module is deprecated. Any additional feature will no longer be merged. */ Module.register("currentweather", { // Default module config. @@ -47,24 +49,24 @@ Module.register("currentweather", { roundTemp: false, iconTable: { - "01d": "wi-day-sunny", - "02d": "wi-day-cloudy", - "03d": "wi-cloudy", - "04d": "wi-cloudy-windy", - "09d": "wi-showers", - "10d": "wi-rain", - "11d": "wi-thunderstorm", - "13d": "wi-snow", - "50d": "wi-fog", - "01n": "wi-night-clear", - "02n": "wi-night-cloudy", - "03n": "wi-night-cloudy", - "04n": "wi-night-cloudy", - "09n": "wi-night-showers", - "10n": "wi-night-rain", - "11n": "wi-night-thunderstorm", - "13n": "wi-night-snow", - "50n": "wi-night-alt-cloudy-windy" + "01d": "day-sunny", + "02d": "day-cloudy", + "03d": "cloudy", + "04d": "cloudy-windy", + "09d": "showers", + "10d": "rain", + "11d": "thunderstorm", + "13d": "snow", + "50d": "fog", + "01n": "night-clear", + "02n": "night-cloudy", + "03n": "night-cloudy", + "04n": "night-cloudy", + "09n": "night-showers", + "10n": "night-rain", + "11n": "night-thunderstorm", + "13n": "night-snow", + "50n": "night-alt-cloudy-windy" } }, @@ -219,7 +221,7 @@ Module.register("currentweather", { if (this.config.hideTemp === false) { var weatherIcon = document.createElement("span"); - weatherIcon.className = "wi weathericon " + this.weatherType; + weatherIcon.className = "wi weathericon wi-" + this.weatherType; large.appendChild(weatherIcon); var temperature = document.createElement("span"); @@ -258,13 +260,9 @@ Module.register("currentweather", { var feelsLike = document.createElement("span"); feelsLike.className = "dimmed"; - var feelsLikeHtml = this.translate("FEELS"); - if (feelsLikeHtml.indexOf("{DEGREE}") > -1) { - feelsLikeHtml = this.translate("FEELS", { - DEGREE: this.feelsLike + degreeLabel - }); - } else feelsLikeHtml += " " + this.feelsLike + degreeLabel; - feelsLike.innerHTML = feelsLikeHtml; + feelsLike.innerHTML = this.translate("FEELS", { + DEGREE: this.feelsLike + degreeLabel + }); small.appendChild(feelsLike); wrapper.appendChild(small); @@ -506,6 +504,7 @@ Module.register("currentweather", { this.loaded = true; this.updateDom(this.config.animationSpeed); this.sendNotification("CURRENTWEATHER_DATA", { data: data }); + this.sendNotification("CURRENTWEATHER_TYPE", { type: this.config.iconTable[data.weather[0].icon].replace("-", "_") }); }, /* scheduleUpdate() @@ -593,6 +592,7 @@ Module.register("currentweather", { */ roundValue: function (temperature) { var decimals = this.config.roundTemp ? 0 : 1; - return parseFloat(temperature).toFixed(decimals); + var roundValue = parseFloat(temperature).toFixed(decimals); + return roundValue === "-0" ? 0 : roundValue; } }); diff --git a/modules/default/currentweather/node_helper.js b/modules/default/currentweather/node_helper.js new file mode 100644 index 00000000..d4c7a14c --- /dev/null +++ b/modules/default/currentweather/node_helper.js @@ -0,0 +1,9 @@ +const NodeHelper = require("node_helper"); +const Log = require("logger"); + +module.exports = NodeHelper.create({ + // Override start method. + start: function () { + Log.warn(`The module '${this.name}' is deprecated in favor of the 'weather'-module, please refer to the documentation for a migration path`); + } +}); diff --git a/modules/default/newsfeed/fullarticle.njk b/modules/default/newsfeed/fullarticle.njk new file mode 100644 index 00000000..6570396e --- /dev/null +++ b/modules/default/newsfeed/fullarticle.njk @@ -0,0 +1,3 @@ +