Merge branch 'develop' into bryanzzhu-weather

This commit is contained in:
Michael Teeuw 2020-09-18 12:15:44 +02:00 committed by GitHub
commit 207a9ba723
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 767 additions and 467 deletions

View File

@ -1,6 +1,6 @@
{ {
"extends": ["eslint:recommended", "plugin:prettier/recommended"], "extends": ["eslint:recommended", "plugin:prettier/recommended", "plugin:jsdoc/recommended"],
"plugins": ["prettier"], "plugins": ["prettier", "jsdoc"],
"env": { "env": {
"browser": true, "browser": true,
"es6": true, "es6": true,

View File

@ -5,33 +5,44 @@ 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² ❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
### fixed
- 2110, 2111, 2118 recurring full day events should not use timezone adjustment. just compare month/day
## [2.13.0] - Unreleased (Develop Branch - Please add your contributions to this release.) ## [2.13.0] - Unreleased (Develop Branch - Please add your contributions to this release.)
_This release is scheduled to be released on 2020-10-01._ _This release is scheduled to be released on 2020-10-01._
### Added ### Added
- `--dry-run` option adde in fetch call within updatenotification node_helper. This is to prevent
MagicMirror from consuming any fetch result. Causes conflict with MMPM when attempting to check
for updates to MagicMirror and/or MagicMirror modules.
- Test coverage with Istanbul, run it with `npm run test:coverage`. - Test coverage with Istanbul, run it with `npm run test:coverage`.
- Add lithuanian language. - Add lithuanian language.
- Added support in weatherforecast for OpenWeather onecall API. - Added support in weatherforecast for OpenWeather onecall API.
- Added config option to calendar-icons for recurring- and fullday-events - Added config option to calendar-icons for recurring- and fullday-events
- Added current, hourly (max 48), and daily (max 7) weather forecasts to weather module via OpenWeatherMap One Call API - Added current, hourly (max 48), and daily (max 7) weather forecasts to weather module via OpenWeatherMap One Call API
- Added eslint-plugin for jsdoc comments
### Updated ### Updated
- Change incorrect weather.js default properties. - Change incorrect weather.js default properties.
- Cleaned up newsfeed module. - Cleaned up newsfeed module.
- Cleaned up jsdoc comments.
- Cleaned up clock tests.
### Deleted ### Deleted
### Fixed ### Fixed
- Fix backward compatibility issues for Safari < 11. [#1985](https://github.com/MichMich/MagicMirror/issues/1985) - Fix backward compatibility issues for Safari < 11.
- Fix the use of "maxNumberOfDays" in the module "weatherforecast depending on the endpoint (forecast/daily or forecast)". [#2018](https://github.com/MichMich/MagicMirror/issues/2018) - Fix the use of "maxNumberOfDays" in the module "weatherforecast depending on the endpoint (forecast/daily or forecast)". [#2018](https://github.com/MichMich/MagicMirror/issues/2018)
- Fix calendar display. Account for current timezone. [#2068](https://github.com/MichMich/MagicMirror/issues/2068) - Fix calendar display. Account for current timezone. [#2068](https://github.com/MichMich/MagicMirror/issues/2068)
- Fix logLevel being set before loading config. - Fix logLevel being set before loading config.
- Fix incorrect namespace links in svg clockfaces. [#2072](https://github.com/MichMich/MagicMirror/issues/2072) - Fix incorrect namespace links in svg clockfaces. [#2072](https://github.com/MichMich/MagicMirror/issues/2072)
- Fix weather/providers/weathergov for API guidelines [#2045] - Fix weather/providers/weathergov for API guidelines. [#2045](https://github.com/MichMich/MagicMirror/issues/2045)
- Fix "undefined" in weather modules header. [#1985](https://github.com/MichMich/MagicMirror/issues/1985)
## [2.12.0] - 2020-07-01 ## [2.12.0] - 2020-07-01
@ -71,6 +82,7 @@ Special thanks to the following contributors: @AndreKoepke, @andrezibaia, @bryan
- Throw error when check_config fails. [#1928](https://github.com/MichMich/MagicMirror/issues/1928) - Throw error when check_config fails. [#1928](https://github.com/MichMich/MagicMirror/issues/1928)
- Bug fix related to 'maxEntries' not displaying Calendar events. [#2050](https://github.com/MichMich/MagicMirror/issues/2050) - Bug fix related to 'maxEntries' not displaying Calendar events. [#2050](https://github.com/MichMich/MagicMirror/issues/2050)
- Updated ical library to latest version. [#1926](https://github.com/MichMich/MagicMirror/issues/1926) - Updated ical library to latest version. [#1926](https://github.com/MichMich/MagicMirror/issues/1926)
- Fix config check after merge of prettier [#2109](https://github.com/MichMich/MagicMirror/issues/2109)
## [2.11.0] - 2020-04-01 ## [2.11.0] - 2020-04-01

View File

@ -4,10 +4,19 @@
(function () { (function () {
var config = {}; var config = {};
// Helper function to get server address/hostname from either the commandline or env /**
* Helper function to get server address/hostname from either the commandline or env
*/
function getServerAddress() { function getServerAddress() {
// Helper function to get command line parameters /**
// Assumes that a cmdline parameter is defined with `--key [value]` * Get command line parameters
* Assumes that a cmdline parameter is defined with `--key [value]`
*
* @param {string} key key to look for at the command line
* @param {string} defaultValue value if no key is given at the command line
*
* @returns {string} the value of the parameter
*/
function getCommandLineParameter(key, defaultValue = undefined) { function getCommandLineParameter(key, defaultValue = undefined) {
var index = process.argv.indexOf(`--${key}`); var index = process.argv.indexOf(`--${key}`);
var value = index > -1 ? process.argv[index + 1] : undefined; var value = index > -1 ? process.argv[index + 1] : undefined;
@ -23,10 +32,17 @@
config["tls"] = process.argv.indexOf("--use-tls") > 0; config["tls"] = process.argv.indexOf("--use-tls") > 0;
} }
/**
* Gets the config from the specified server url
*
* @param {string} url location where the server is running.
*
* @returns {Promise} the config
*/
function getServerConfig(url) { function getServerConfig(url) {
// Return new pending promise // Return new pending promise
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Select http or https module, depending on reqested url // Select http or https module, depending on requested url
const lib = url.startsWith("https") ? require("https") : require("http"); const lib = url.startsWith("https") ? require("https") : require("http");
const request = lib.get(url, (response) => { const request = lib.get(url, (response) => {
var configData = ""; var configData = "";
@ -47,6 +63,12 @@
}); });
} }
/**
* Print a message to the console in case of errors
*
* @param {string} [message] error message to print
* @param {number} code error code for the exit call
*/
function fail(message, code = 1) { function fail(message, code = 1) {
if (message !== undefined && typeof message === "string") { if (message !== undefined && typeof message === "string") {
console.log(message); console.log(message);

View File

@ -40,16 +40,19 @@ process.on("uncaughtException", function (err) {
Log.error("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues"); Log.error("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues");
}); });
/* App - The core app. /**
* The core app.
*
* @class
*/ */
var App = function () { var App = function () {
var nodeHelpers = []; var nodeHelpers = [];
/* loadConfig(callback) /**
* Loads the config file. combines it with the defaults, * Loads the config file. Combines it with the defaults, and runs the
* and runs the callback with the found config as argument. * callback with the found config as argument.
* *
* argument callback function - The callback function. * @param {Function} callback Function to be called after loading the config
*/ */
var loadConfig = function (callback) { var loadConfig = function (callback) {
Log.log("Loading config ..."); Log.log("Loading config ...");
@ -80,6 +83,12 @@ var App = function () {
} }
}; };
/**
* Checks the config for deprecated options and throws a warning in the logs
* if it encounters one option from the deprecated.js list
*
* @param {object} userConfig The user config
*/
var checkDeprecatedOptions = function (userConfig) { var checkDeprecatedOptions = function (userConfig) {
var deprecated = require(global.root_path + "/js/deprecated.js"); var deprecated = require(global.root_path + "/js/deprecated.js");
var deprecatedOptions = deprecated.configs; var deprecatedOptions = deprecated.configs;
@ -96,10 +105,11 @@ var App = function () {
} }
}; };
/* loadModule(module) /**
* Loads a specific module. * Loads a specific module.
* *
* argument module string - The name of the module (including subpath). * @param {string} module The name of the module (including subpath).
* @param {Function} callback Function to be called after loading
*/ */
var loadModule = function (module, callback) { var loadModule = function (module, callback) {
var elements = module.split("/"); var elements = module.split("/");
@ -144,10 +154,11 @@ var App = function () {
} }
}; };
/* loadModules(modules) /**
* Loads all modules. * Loads all modules.
* *
* argument module string - The name of the module (including subpath). * @param {Module[]} modules All modules to be loaded
* @param {Function} callback Function to be called after loading
*/ */
var loadModules = function (modules, callback) { var loadModules = function (modules, callback) {
Log.log("Loading module helpers ..."); Log.log("Loading module helpers ...");
@ -169,11 +180,14 @@ var App = function () {
loadNextModule(); loadNextModule();
}; };
/* cmpVersions(a,b) /**
* Compare two semantic version numbers and return the difference. * Compare two semantic version numbers and return the difference.
* *
* argument a string - Version number a. * @param {string} a Version number a.
* argument a string - Version number b. * @param {string} b Version number b.
*
* @returns {number} A positive number if a is larger than b, a negative
* number if a is smaller and 0 if they are the same
*/ */
function cmpVersions(a, b) { function cmpVersions(a, b) {
var i, diff; var i, diff;
@ -191,12 +205,13 @@ var App = function () {
return segmentsA.length - segmentsB.length; return segmentsA.length - segmentsB.length;
} }
/* start(callback) /**
* This methods starts the core app. * Start the core app.
* It loads the config, then it loads all modules.
* When it's done it executes the callback with the config as argument.
* *
* argument callback function - The callback function. * It loads the config, then it loads all modules. When it's done it
* executes the callback with the config as argument.
*
* @param {Function} callback Function to be called after start
*/ */
this.start = function (callback) { this.start = function (callback) {
loadConfig(function (c) { loadConfig(function (c) {
@ -234,9 +249,10 @@ var App = function () {
}); });
}; };
/* stop() /**
* This methods stops the core app. * Stops the core app. This calls each node_helper's STOP() function, if it
* This calls each node_helper's STOP() function, if it exists. * exists.
*
* Added to fix #1056 * Added to fix #1056
*/ */
this.stop = function () { this.stop = function () {
@ -248,7 +264,8 @@ var App = function () {
} }
}; };
/* Listen for SIGINT signal and call stop() function. /**
* Listen for SIGINT signal and call stop() function.
* *
* Added to fix #1056 * Added to fix #1056
* Note: this is only used if running `server-only`. Otherwise * Note: this is only used if running `server-only`. Otherwise
@ -263,7 +280,9 @@ var App = function () {
process.exit(0); process.exit(0);
}); });
/* We also need to listen to SIGTERM signals so we stop everything when we are asked to stop by the OS. /**
* Listen to SIGTERM signals so we can stop everything when we
* are asked to stop by the OS.
*/ */
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
Log.log("[SIGTERM] Received. Shutting down server..."); Log.log("[SIGTERM] Received. Shutting down server...");

View File

@ -12,13 +12,14 @@ const path = require("path");
const fs = require("fs"); const fs = require("fs");
const rootPath = path.resolve(__dirname + "/../"); const rootPath = path.resolve(__dirname + "/../");
const config = require(rootPath + "/.eslintrc.json");
const Log = require(rootPath + "/js/logger.js"); const Log = require(rootPath + "/js/logger.js");
const Utils = require(rootPath + "/js/utils.js"); const Utils = require(rootPath + "/js/utils.js");
/* getConfigFile() /**
* Return string with path of configuration file * Returns a string with path of configuration file.
* Check if set by environment variable MM_CONFIG_FILE * Check if set by environment variable MM_CONFIG_FILE
*
* @returns {string} path and filename of the config file
*/ */
function getConfigFile() { function getConfigFile() {
// FIXME: This function should be in core. Do you want refactor me ;) ?, be good! // FIXME: This function should be in core. Do you want refactor me ;) ?, be good!
@ -29,6 +30,9 @@ function getConfigFile() {
return configFileName; return configFileName;
} }
/**
* Checks the config file using eslint.
*/
function checkConfigFile() { function checkConfigFile() {
const configFileName = getConfigFile(); const configFileName = getConfigFile();
@ -38,7 +42,7 @@ function checkConfigFile() {
throw new Error("No config file present!"); throw new Error("No config file present!");
} }
// check permission // Check permission
try { try {
fs.accessSync(configFileName, fs.F_OK); fs.accessSync(configFileName, fs.F_OK);
} catch (e) { } catch (e) {
@ -54,16 +58,15 @@ function checkConfigFile() {
if (err) { if (err) {
throw err; throw err;
} }
const messages = linter.verify(data, config); const messages = linter.verify(data);
if (messages.length === 0) { if (messages.length === 0) {
Log.log("Your configuration file doesn't contain syntax errors :)"); Log.info(Utils.colors.pass("Your configuration file doesn't contain syntax errors :)"));
return true;
} else { } else {
Log.error(Utils.colors.error("Your configuration file contains syntax errors :("));
// In case the there errors show messages and return // In case the there errors show messages and return
messages.forEach((error) => { messages.forEach((error) => {
Log.log("Line", error.line, "col", error.column, error.message); Log.error("Line", error.line, "col", error.column, error.message);
}); });
throw new Error("Wrong syntax in config file!");
} }
}); });
} }

View File

@ -57,7 +57,9 @@
: prop[name]; : prop[name];
} }
// The dummy class constructor /**
* The dummy class constructor
*/
function Class() { function Class() {
// All construction is actually done in the init method // All construction is actually done in the init method
if (!initializing && this.init) { if (!initializing && this.init) {
@ -78,8 +80,13 @@
}; };
})(); })();
//Define the clone method for later use. /**
//Helper Method * Define the clone method for later use. Helper Method.
*
* @param {object} obj Object to be cloned
*
* @returns {object} the cloned object
*/
function cloneObject(obj) { function cloneObject(obj) {
if (obj === null || typeof obj !== "object") { if (obj === null || typeof obj !== "object") {
return obj; return obj;

View File

@ -15,6 +15,9 @@ const BrowserWindow = electron.BrowserWindow;
// be closed automatically when the JavaScript object is garbage collected. // be closed automatically when the JavaScript object is garbage collected.
let mainWindow; let mainWindow;
/**
*
*/
function createWindow() { function createWindow() {
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
var electronOptionsDefaults = { var electronOptionsDefaults = {

View File

@ -15,7 +15,7 @@ var Loader = (function () {
/* Private Methods */ /* Private Methods */
/* loadModules() /**
* Loops thru all modules and requests load for every module. * Loops thru all modules and requests load for every module.
*/ */
var loadModules = function () { var loadModules = function () {
@ -43,7 +43,7 @@ var Loader = (function () {
loadNextModule(); loadNextModule();
}; };
/* startModules() /**
* Loops thru all modules and requests start for every module. * Loops thru all modules and requests start for every module.
*/ */
var startModules = function () { var startModules = function () {
@ -56,19 +56,19 @@ var Loader = (function () {
MM.modulesStarted(moduleObjects); MM.modulesStarted(moduleObjects);
}; };
/* getAllModules() /**
* Retrieve list of all modules. * Retrieve list of all modules.
* *
* return array - module data as configured in config * @returns {object[]} module data as configured in config
*/ */
var getAllModules = function () { var getAllModules = function () {
return config.modules; return config.modules;
}; };
/* getModuleData() /**
* Generate array with module information including module paths. * Generate array with module information including module paths.
* *
* return array - Module information. * @returns {object[]} Module information.
*/ */
var getModuleData = function () { var getModuleData = function () {
var modules = getAllModules(); var modules = getAllModules();
@ -106,11 +106,11 @@ var Loader = (function () {
return moduleFiles; return moduleFiles;
}; };
/* loadModule(module) /**
* Load modules via ajax request and create module objects. * Load modules via ajax request and create module objects.s
* *
* argument callback function - Function called when done. * @param {object} module Information about the module we want to load.
* argument module object - Information about the module we want to load. * @param {Function} callback Function called when done.
*/ */
var loadModule = function (module, callback) { var loadModule = function (module, callback) {
var url = module.path + "/" + module.file; var url = module.path + "/" + module.file;
@ -136,12 +136,12 @@ var Loader = (function () {
} }
}; };
/* bootstrapModule(module, mObj) /**
* Bootstrap modules by setting the module data and loading the scripts & styles. * Bootstrap modules by setting the module data and loading the scripts & styles.
* *
* argument module object - Information about the module we want to load. * @param {object} module Information about the module we want to load.
* argument mObj object - Modules instance. * @param {Module} mObj Modules instance.
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
var bootstrapModule = function (module, mObj, callback) { var bootstrapModule = function (module, mObj, callback) {
Log.info("Bootstrapping module: " + module.name); Log.info("Bootstrapping module: " + module.name);
@ -161,11 +161,11 @@ var Loader = (function () {
}); });
}; };
/* loadFile(fileName) /**
* Load a script or stylesheet by adding it to the dom. * Load a script or stylesheet by adding it to the dom.
* *
* argument fileName string - Path of the file we want to load. * @param {string} fileName Path of the file we want to load.
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
var loadFile = function (fileName, callback) { var loadFile = function (fileName, callback) {
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1); var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
@ -215,20 +215,20 @@ var Loader = (function () {
/* Public Methods */ /* Public Methods */
return { return {
/* loadModules() /**
* Load all modules as defined in the config. * Load all modules as defined in the config.
*/ */
loadModules: function () { loadModules: function () {
loadModules(); loadModules();
}, },
/* loadFile() /**
* Load a file (script or stylesheet). * Load a file (script or stylesheet).
* Prevent double loading and search for files in the vendor folder. * Prevent double loading and search for files in the vendor folder.
* *
* argument fileName string - Path of the file we want to load. * @param {string} fileName Path of the file we want to load.
* argument module Module Object - the module that calls the loadFile function. * @param {Module} module The module that calls the loadFile function.
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
loadFile: function (fileName, module, callback) { loadFile: function (fileName, module, callback) {
if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) { if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) {

View File

@ -11,9 +11,8 @@ var MM = (function () {
/* Private Methods */ /* Private Methods */
/* createDomObjects() /**
* Create dom objects for all modules that * Create dom objects for all modules that are configured for a specific position.
* are configured for a specific position.
*/ */
var createDomObjects = function () { var createDomObjects = function () {
var domCreationPromises = []; var domCreationPromises = [];
@ -43,6 +42,8 @@ var MM = (function () {
if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") { if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") {
moduleHeader.style.display = "none;"; moduleHeader.style.display = "none;";
} else {
moduleHeader.style.display = "block;";
} }
var moduleContent = document.createElement("div"); var moduleContent = document.createElement("div");
@ -65,10 +66,12 @@ var MM = (function () {
}); });
}; };
/* selectWrapper(position) /**
* Select the wrapper dom object for a specific position. * Select the wrapper dom object for a specific position.
* *
* argument position string - The name of the position. * @param {string} position The name of the position.
*
* @returns {HTMLElement} the wrapper element
*/ */
var selectWrapper = function (position) { var selectWrapper = function (position) {
var classes = position.replace("_", " "); var classes = position.replace("_", " ");
@ -81,13 +84,13 @@ var MM = (function () {
} }
}; };
/* sendNotification(notification, payload, sender) /**
* Send a notification to all modules. * Send a notification to all modules.
* *
* argument notification string - The identifier of the notification. * @param {string} notification The identifier of the notification.
* argument payload mixed - The payload of the notification. * @param {*} payload The payload of the notification.
* argument sender Module - The module that sent the notification. * @param {Module} sender The module that sent the notification.
* argument sendTo Module - The module to send the notification to. (optional) * @param {Module} [sendTo] The (optional) module to send the notification to.
*/ */
var sendNotification = function (notification, payload, sender, sendTo) { var sendNotification = function (notification, payload, sender, sendTo) {
for (var m in modules) { for (var m in modules) {
@ -98,13 +101,13 @@ var MM = (function () {
} }
}; };
/* updateDom(module, speed) /**
* Update the dom for a specific module. * Update the dom for a specific module.
* *
* argument module Module - The module that needs an update. * @param {Module} module The module that needs an update.
* argument speed Number - The number of microseconds for the animation. (optional) * @param {number} [speed] The (optional) number of microseconds for the animation.
* *
* return Promise - Resolved when the dom is fully updated. * @returns {Promise} Resolved when the dom is fully updated.
*/ */
var updateDom = function (module, speed) { var updateDom = function (module, speed) {
return new Promise(function (resolve) { return new Promise(function (resolve) {
@ -126,15 +129,15 @@ var MM = (function () {
}); });
}; };
/* updateDomWithContent(module, speed, newHeader, newContent) /**
* Update the dom with the specified content * Update the dom with the specified content
* *
* argument module Module - The module that needs an update. * @param {Module} module The module that needs an update.
* argument speed Number - The number of microseconds for the animation. (optional) * @param {number} [speed] The (optional) number of microseconds for the animation.
* argument newHeader String - The new header that is generated. * @param {string} newHeader The new header that is generated.
* argument newContent Domobject - The new content that is generated. * @param {HTMLElement} newContent The new content that is generated.
* *
* return Promise - Resolved when the module dom has been updated. * @returns {Promise} Resolved when the module dom has been updated.
*/ */
var updateDomWithContent = function (module, speed, newHeader, newContent) { var updateDomWithContent = function (module, speed, newHeader, newContent) {
return new Promise(function (resolve) { return new Promise(function (resolve) {
@ -165,14 +168,14 @@ var MM = (function () {
}); });
}; };
/* moduleNeedsUpdate(module, newContent) /**
* Check if the content has changed. * Check if the content has changed.
* *
* argument module Module - The module to check. * @param {Module} module The module to check.
* argument newHeader String - The new header that is generated. * @param {string} newHeader The new header that is generated.
* argument newContent Domobject - The new content that is generated. * @param {HTMLElement} newContent The new content that is generated.
* *
* return bool - Does the module need an update? * @returns {boolean} True if the module need an update, false otherwise
*/ */
var moduleNeedsUpdate = function (module, newHeader, newContent) { var moduleNeedsUpdate = function (module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier); var moduleWrapper = document.getElementById(module.identifier);
@ -197,12 +200,12 @@ var MM = (function () {
return headerNeedsUpdate || contentNeedsUpdate; return headerNeedsUpdate || contentNeedsUpdate;
}; };
/* moduleNeedsUpdate(module, newContent) /**
* Update the content of a module on screen. * Update the content of a module on screen.
* *
* argument module Module - The module to check. * @param {Module} module The module to check.
* argument newHeader String - The new header that is generated. * @param {string} newHeader The new header that is generated.
* argument newContent Domobject - The new content that is generated. * @param {HTMLElement} newContent The new content that is generated.
*/ */
var updateModuleContent = function (module, newHeader, newContent) { var updateModuleContent = function (module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier); var moduleWrapper = document.getElementById(module.identifier);
@ -217,18 +220,19 @@ var MM = (function () {
headerWrapper[0].innerHTML = newHeader; headerWrapper[0].innerHTML = newHeader;
if (headerWrapper.length > 0 && newHeader) { if (headerWrapper.length > 0 && newHeader) {
delete headerWrapper[0].style; headerWrapper[0].style.display = "block";
} else { } else {
headerWrapper[0].style.display = "none"; headerWrapper[0].style.display = "none";
} }
}; };
/* hideModule(module, speed, callback) /**
* Hide the module. * Hide the module.
* *
* argument module Module - The module to hide. * @param {Module} module The module to hide.
* argument speed Number - The speed of the hide animation. * @param {number} speed The speed of the hide animation.
* argument callback function - Called when the animation is done. * @param {Function} callback Called when the animation is done.
* @param {object} [options] Optional settings for the hide method.
*/ */
var hideModule = function (module, speed, callback, options) { var hideModule = function (module, speed, callback, options) {
options = options || {}; options = options || {};
@ -268,12 +272,13 @@ var MM = (function () {
} }
}; };
/* showModule(module, speed, callback) /**
* Show the module. * Show the module.
* *
* argument module Module - The module to show. * @param {Module} module The module to show.
* argument speed Number - The speed of the show animation. * @param {number} speed The speed of the show animation.
* argument callback function - Called when the animation is done. * @param {Function} callback Called when the animation is done.
* @param {object} [options] Optional settings for the show method.
*/ */
var showModule = function (module, speed, callback, options) { var showModule = function (module, speed, callback, options) {
options = options || {}; options = options || {};
@ -327,7 +332,7 @@ var MM = (function () {
} }
}; };
/* updateWrapperStates() /**
* Checks for all positions if it has visible content. * Checks for all positions if it has visible content.
* If not, if will hide the position to prevent unwanted margins. * If not, if will hide the position to prevent unwanted margins.
* This method should be called by the show and hide methods. * This method should be called by the show and hide methods.
@ -356,8 +361,8 @@ var MM = (function () {
}); });
}; };
/* loadConfig() /**
* Loads the core config and combines it with de system defaults. * Loads the core config and combines it with the system defaults.
*/ */
var loadConfig = function () { var loadConfig = function () {
// FIXME: Think about how to pass config around without breaking tests // FIXME: Think about how to pass config around without breaking tests
@ -372,41 +377,41 @@ var MM = (function () {
/* eslint-enable */ /* eslint-enable */
}; };
/* setSelectionMethodsForModules() /**
* Adds special selectors on a collection of modules. * Adds special selectors on a collection of modules.
* *
* argument modules array - Array of modules. * @param {Module[]} modules Array of modules.
*/ */
var setSelectionMethodsForModules = function (modules) { var setSelectionMethodsForModules = function (modules) {
/* withClass(className) /**
* calls modulesByClass to filter modules with the specified classes. * Filter modules with the specified classes.
* *
* argument className string/array - one or multiple classnames. (array or space divided) * @param {string|string[]} className one or multiple classnames (array or space divided).
* *
* return array - Filtered collection of modules. * @returns {Module[]} Filtered collection of modules.
*/ */
var withClass = function (className) { var withClass = function (className) {
return modulesByClass(className, true); return modulesByClass(className, true);
}; };
/* exceptWithClass(className) /**
* calls modulesByClass to filter modules without the specified classes. * Filter modules without the specified classes.
* *
* argument className string/array - one or multiple classnames. (array or space divided) * @param {string|string[]} className one or multiple classnames (array or space divided).
* *
* return array - Filtered collection of modules. * @returns {Module[]} Filtered collection of modules.
*/ */
var exceptWithClass = function (className) { var exceptWithClass = function (className) {
return modulesByClass(className, false); return modulesByClass(className, false);
}; };
/* modulesByClass(className, include) /**
* filters a collection of modules based on classname(s). * Filters a collection of modules based on classname(s).
* *
* argument className string/array - one or multiple classnames. (array or space divided) * @param {string|string[]} className one or multiple classnames (array or space divided).
* argument include boolean - if the filter should include or exclude the modules with the specific classes. * @param {boolean} include if the filter should include or exclude the modules with the specific classes.
* *
* return array - Filtered collection of modules. * @returns {Module[]} Filtered collection of modules.
*/ */
var modulesByClass = function (className, include) { var modulesByClass = function (className, include) {
var searchClasses = className; var searchClasses = className;
@ -431,12 +436,12 @@ var MM = (function () {
return newModules; return newModules;
}; };
/* exceptModule(module) /**
* Removes a module instance from the collection. * Removes a module instance from the collection.
* *
* argument module Module object - The module instance to remove from the collection. * @param {object} module The module instance to remove from the collection.
* *
* return array - Filtered collection of modules. * @returns {Module[]} Filtered collection of modules.
*/ */
var exceptModule = function (module) { var exceptModule = function (module) {
var newModules = modules.filter(function (mod) { var newModules = modules.filter(function (mod) {
@ -447,10 +452,10 @@ var MM = (function () {
return newModules; return newModules;
}; };
/* enumerate(callback) /**
* Walks thru a collection of modules and executes the callback with the module as an argument. * Walks thru a collection of modules and executes the callback with the module as an argument.
* *
* argument callback function - The function to execute with the module as an argument. * @param {Function} callback The function to execute with the module as an argument.
*/ */
var enumerate = function (callback) { var enumerate = function (callback) {
modules.map(function (module) { modules.map(function (module) {
@ -475,7 +480,7 @@ var MM = (function () {
return { return {
/* Public Methods */ /* Public Methods */
/* init() /**
* Main init method. * Main init method.
*/ */
init: function () { init: function () {
@ -488,10 +493,10 @@ var MM = (function () {
Loader.loadModules(); Loader.loadModules();
}, },
/* modulesStarted(moduleObjects) /**
* Gets called when all modules are started. * Gets called when all modules are started.
* *
* argument moduleObjects array<Module> - All module instances. * @param {Module[]} moduleObjects All module instances.
*/ */
modulesStarted: function (moduleObjects) { modulesStarted: function (moduleObjects) {
modules = []; modules = [];
@ -506,12 +511,12 @@ var MM = (function () {
createDomObjects(); createDomObjects();
}, },
/* sendNotification(notification, payload, sender) /**
* Send a notification to all modules. * Send a notification to all modules.
* *
* argument notification string - The identifier of the notification. * @param {string} notification The identifier of the notification.
* argument payload mixed - The payload of the notification. * @param {*} payload The payload of the notification.
* argument sender Module - The module that sent the notification. * @param {Module} sender The module that sent the notification.
*/ */
sendNotification: function (notification, payload, sender) { sendNotification: function (notification, payload, sender) {
if (arguments.length < 3) { if (arguments.length < 3) {
@ -533,11 +538,11 @@ var MM = (function () {
sendNotification(notification, payload, sender); sendNotification(notification, payload, sender);
}, },
/* updateDom(module, speed) /**
* Update the dom for a specific module. * Update the dom for a specific module.
* *
* argument module Module - The module that needs an update. * @param {Module} module The module that needs an update.
* argument speed Number - The number of microseconds for the animation. (optional) * @param {number} [speed] The number of microseconds for the animation.
*/ */
updateDom: function (module, speed) { updateDom: function (module, speed) {
if (!(module instanceof Module)) { if (!(module instanceof Module)) {
@ -549,36 +554,36 @@ var MM = (function () {
updateDom(module, speed); updateDom(module, speed);
}, },
/* getModules(module, speed) /**
* Returns a collection of all modules currently active. * Returns a collection of all modules currently active.
* *
* return array - A collection of all modules currently active. * @returns {Module[]} A collection of all modules currently active.
*/ */
getModules: function () { getModules: function () {
setSelectionMethodsForModules(modules); setSelectionMethodsForModules(modules);
return modules; return modules;
}, },
/* hideModule(module, speed, callback) /**
* Hide the module. * Hide the module.
* *
* argument module Module - The module hide. * @param {Module} module The module to hide.
* argument speed Number - The speed of the hide animation. * @param {number} speed The speed of the hide animation.
* argument callback function - Called when the animation is done. * @param {Function} callback Called when the animation is done.
* argument options object - Optional settings for the hide method. * @param {object} [options] Optional settings for the hide method.
*/ */
hideModule: function (module, speed, callback, options) { hideModule: function (module, speed, callback, options) {
module.hidden = true; module.hidden = true;
hideModule(module, speed, callback, options); hideModule(module, speed, callback, options);
}, },
/* showModule(module, speed, callback) /**
* Show the module. * Show the module.
* *
* argument module Module - The module show. * @param {Module} module The module to show.
* argument speed Number - The speed of the show animation. * @param {number} speed The speed of the show animation.
* argument callback function - Called when the animation is done. * @param {Function} callback Called when the animation is done.
* argument options object - Optional settings for the hide method. * @param {object} [options] Optional settings for the show method.
*/ */
showModule: function (module, speed, callback, options) { showModule: function (module, speed, callback, options) {
// do not change module.hidden yet, only if we really show it later // do not change module.hidden yet, only if we really show it later

View File

@ -2,9 +2,11 @@
/* Magic Mirror /* Magic Mirror
* Module Blueprint. * Module Blueprint.
* @typedef {Object} Module
* *
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*
*/ */
var Module = Class.extend({ var Module = Class.extend({
/********************************************************* /*********************************************************
@ -29,53 +31,55 @@ var Module = Class.extend({
// Use the nunjucksEnvironment() to get it. // Use the nunjucksEnvironment() to get it.
_nunjucksEnvironment: null, _nunjucksEnvironment: null,
/* init() /**
* Is called when the module is instantiated. * Called when the module is instantiated.
*/ */
init: function () { init: function () {
//Log.log(this.defaults); //Log.log(this.defaults);
}, },
/* start() /**
* Is called when the module is started. * Called when the module is started.
*/ */
start: function () { start: function () {
Log.info("Starting module: " + this.name); Log.info("Starting module: " + this.name);
}, },
/* getScripts() /**
* Returns a list of scripts the module requires to be loaded. * Returns a list of scripts the module requires to be loaded.
* *
* return Array<String> - An array with filenames. * @returns {string[]} An array with filenames.
*/ */
getScripts: function () { getScripts: function () {
return []; return [];
}, },
/* getStyles() /**
* Returns a list of stylesheets the module requires to be loaded. * Returns a list of stylesheets the module requires to be loaded.
* *
* return Array<String> - An array with filenames. * @returns {string[]} An array with filenames.
*/ */
getStyles: function () { getStyles: function () {
return []; return [];
}, },
/* getTranslations() /**
* Returns a map of translation files the module requires to be loaded. * Returns a map of translation files the module requires to be loaded.
* *
* return Map<String, String> - A map with langKeys and filenames. * return Map<String, String> -
*
* @returns {*} A map with langKeys and filenames.
*/ */
getTranslations: function () { getTranslations: function () {
return false; return false;
}, },
/* getDom() /**
* This method generates the dom which needs to be displayed. This method is called by the Magic Mirror core. * Generates the dom which needs to be displayed. This method is called by the Magic Mirror core.
* This method can to be subclassed if the module wants to display info on the mirror. * This method can to be subclassed if the module wants to display info on the mirror.
* Alternatively, the getTemplate method could be subclassed. * Alternatively, the getTemplate method could be subclassed.
* *
* return DomObject | Promise - The dom or a promise with the dom to display. * @returns {HTMLElement|Promise} The dom or a promise with the dom to display.
*/ */
getDom: function () { getDom: function () {
var self = this; var self = this;
@ -105,46 +109,45 @@ var Module = Class.extend({
}); });
}, },
/* getHeader() /**
* This method generates the header string which needs to be displayed if a user has a header configured for this module. * Generates the header string which needs to be displayed if a user has a header configured for this module.
* This method is called by the Magic Mirror core, but only if the user has configured a default header for the module. * This method is called by the Magic Mirror core, but only if the user has configured a default header for the module.
* This method needs to be subclassed if the module wants to display modified headers on the mirror. * This method needs to be subclassed if the module wants to display modified headers on the mirror.
* *
* return string - The header to display above the header. * @returns {string} The header to display above the header.
*/ */
getHeader: function () { getHeader: function () {
return this.data.header; return this.data.header;
}, },
/* getTemplate() /**
* This method returns the template for the module which is used by the default getDom implementation. * Returns the template for the module which is used by the default getDom implementation.
* This method needs to be subclassed if the module wants to use a template. * This method needs to be subclassed if the module wants to use a template.
* It can either return a template sting, or a template filename. * It can either return a template sting, or a template filename.
* If the string ends with '.html' it's considered a file from within the module's folder. * If the string ends with '.html' it's considered a file from within the module's folder.
* *
* return string - The template string of filename. * @returns {string} The template string of filename.
*/ */
getTemplate: function () { getTemplate: function () {
return '<div class="normal">' + this.name + '</div><div class="small dimmed">' + this.identifier + "</div>"; return '<div class="normal">' + this.name + '</div><div class="small dimmed">' + this.identifier + "</div>";
}, },
/* getTemplateData() /**
* This method returns the data to be used in the template. * Returns the data to be used in the template.
* This method needs to be subclassed if the module wants to use a custom data. * This method needs to be subclassed if the module wants to use a custom data.
* *
* return Object * @returns {object} The data for the template
*/ */
getTemplateData: function () { getTemplateData: function () {
return {}; return {};
}, },
/* notificationReceived(notification, payload, sender) /**
* This method is called when a notification arrives. * Called by the Magic Mirror core when a notification arrives.
* This method is called by the Magic Mirror core.
* *
* argument notification string - The identifier of the notification. * @param {string} notification The identifier of the notification.
* argument payload mixed - The payload of the notification. * @param {*} payload The payload of the notification.
* argument sender Module - The module that sent the notification. * @param {Module} sender The module that sent the notification.
*/ */
notificationReceived: function (notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
if (sender) { if (sender) {
@ -154,11 +157,11 @@ var Module = Class.extend({
} }
}, },
/** nunjucksEnvironment() /**
* Returns the nunjucks environment for the current module. * Returns the nunjucks environment for the current module.
* The environment is checked in the _nunjucksEnvironment instance variable. * The environment is checked in the _nunjucksEnvironment instance variable.
*
* @returns Nunjucks Environment * @returns {object} The Nunjucks Environment
*/ */
nunjucksEnvironment: function () { nunjucksEnvironment: function () {
if (this._nunjucksEnvironment !== null) { if (this._nunjucksEnvironment !== null) {
@ -171,6 +174,7 @@ var Module = Class.extend({
trimBlocks: true, trimBlocks: true,
lstripBlocks: true lstripBlocks: true
}); });
this._nunjucksEnvironment.addFilter("translate", function (str) { this._nunjucksEnvironment.addFilter("translate", function (str) {
return self.translate(str); return self.translate(str);
}); });
@ -178,25 +182,25 @@ var Module = Class.extend({
return this._nunjucksEnvironment; return this._nunjucksEnvironment;
}, },
/* socketNotificationReceived(notification, payload) /**
* This method is called when a socket notification arrives. * Called when a socket notification arrives.
* *
* argument notification string - The identifier of the notification. * @param {string} notification The identifier of the notification.
* argument payload mixed - The payload of the notification. * @param {*} payload The payload of the notification.
*/ */
socketNotificationReceived: function (notification, payload) { socketNotificationReceived: function (notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload); Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
}, },
/* suspend() /*
* This method is called when a module is hidden. * Called when the module is hidden.
*/ */
suspend: function () { suspend: function () {
Log.log(this.name + " is suspended."); Log.log(this.name + " is suspended.");
}, },
/* resume() /*
* This method is called when a module is shown. * Called when the module is shown.
*/ */
resume: function () { resume: function () {
Log.log(this.name + " is resumed."); Log.log(this.name + " is resumed.");
@ -206,10 +210,10 @@ var Module = Class.extend({
* The methods below don"t need subclassing. * * The methods below don"t need subclassing. *
*********************************************/ *********************************************/
/* setData(data) /**
* Set the module data. * Set the module data.
* *
* argument data object - Module data. * @param {Module} data The module data
*/ */
setData: function (data) { setData: function (data) {
this.data = data; this.data = data;
@ -220,18 +224,20 @@ var Module = Class.extend({
this.setConfig(data.config); this.setConfig(data.config);
}, },
/* setConfig(config) /**
* Set the module config and combine it with the module defaults. * Set the module config and combine it with the module defaults.
* *
* argument config object - Module config. * @param {object} config The combined module config.
*/ */
setConfig: function (config) { setConfig: function (config) {
this.config = Object.assign({}, this.defaults, config); this.config = Object.assign({}, this.defaults, config);
}, },
/* socket() /**
* Returns a socket object. If it doesn't exist, it"s created. * Returns a socket object. If it doesn't exist, it's created.
* It also registers the notification callback. * It also registers the notification callback.
*
* @returns {MMSocket} a socket object
*/ */
socket: function () { socket: function () {
if (typeof this._socket === "undefined") { if (typeof this._socket === "undefined") {
@ -246,40 +252,39 @@ var Module = Class.extend({
return this._socket; return this._socket;
}, },
/* file(file) /**
* Retrieve the path to a module file. * Retrieve the path to a module file.
* *
* argument file string - Filename. * @param {string} file Filename
* * @returns {string} the file path
* return string - File path.
*/ */
file: function (file) { file: function (file) {
return (this.data.path + "/" + file).replace("//", "/"); return (this.data.path + "/" + file).replace("//", "/");
}, },
/* loadStyles() /**
* Load all required stylesheets by requesting the MM object to load the files. * Load all required stylesheets by requesting the MM object to load the files.
* *
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
loadStyles: function (callback) { loadStyles: function (callback) {
this.loadDependencies("getStyles", callback); this.loadDependencies("getStyles", callback);
}, },
/* loadScripts() /**
* Load all required scripts by requesting the MM object to load the files. * Load all required scripts by requesting the MM object to load the files.
* *
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
loadScripts: function (callback) { loadScripts: function (callback) {
this.loadDependencies("getScripts", callback); this.loadDependencies("getScripts", callback);
}, },
/* loadDependencies(funcName, callback) /**
* Helper method to load all dependencies. * Helper method to load all dependencies.
* *
* argument funcName string - Function name to call to get scripts or styles. * @param {string} funcName Function name to call to get scripts or styles.
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
loadDependencies: function (funcName, callback) { loadDependencies: function (funcName, callback) {
var self = this; var self = this;
@ -300,10 +305,10 @@ var Module = Class.extend({
loadNextDependency(); loadNextDependency();
}, },
/* loadScripts() /**
* Load all required scripts by requesting the MM object to load the files. * Load all translations.
* *
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
loadTranslations: function (callback) { loadTranslations: function (callback) {
var self = this; var self = this;
@ -334,12 +339,13 @@ var Module = Class.extend({
} }
}, },
/* translate(key, defaultValueOrVariables, defaultValue) /**
* Request the translation for a given key with optional variables and default value. * Request the translation for a given key with optional variables and default value.
* *
* argument key string - The key of the string to translate * @param {string} key The key of the string to translate
* argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional) * @param {string|object} [defaultValueOrVariables] The default value or variables for translating.
* argument defaultValue string - The default value with variables. (Optional) * @param {string} [defaultValue] The default value with variables.
* @returns {string} the translated key
*/ */
translate: function (key, defaultValueOrVariables, defaultValue) { translate: function (key, defaultValueOrVariables, defaultValue) {
if (typeof defaultValueOrVariables === "object") { if (typeof defaultValueOrVariables === "object") {
@ -348,41 +354,41 @@ var Module = Class.extend({
return Translator.translate(this, key) || defaultValueOrVariables || ""; return Translator.translate(this, key) || defaultValueOrVariables || "";
}, },
/* updateDom(speed) /**
* Request an (animated) update of the module. * Request an (animated) update of the module.
* *
* argument speed Number - The speed of the animation. (Optional) * @param {number} [speed] The speed of the animation.
*/ */
updateDom: function (speed) { updateDom: function (speed) {
MM.updateDom(this, speed); MM.updateDom(this, speed);
}, },
/* sendNotification(notification, payload) /**
* Send a notification to all modules. * Send a notification to all modules.
* *
* argument notification string - The identifier of the notification. * @param {string} notification The identifier of the notification.
* argument payload mixed - The payload of the notification. * @param {*} payload The payload of the notification.
*/ */
sendNotification: function (notification, payload) { sendNotification: function (notification, payload) {
MM.sendNotification(notification, payload, this); MM.sendNotification(notification, payload, this);
}, },
/* sendSocketNotification(notification, payload) /**
* Send a socket notification to the node helper. * Send a socket notification to the node helper.
* *
* argument notification string - The identifier of the notification. * @param {string} notification The identifier of the notification.
* argument payload mixed - The payload of the notification. * @param {*} payload The payload of the notification.
*/ */
sendSocketNotification: function (notification, payload) { sendSocketNotification: function (notification, payload) {
this.socket().sendNotification(notification, payload); this.socket().sendNotification(notification, payload);
}, },
/* hideModule(module, speed, callback) /**
* Hide this module. * Hide this module.
* *
* argument speed Number - The speed of the hide animation. * @param {number} speed The speed of the hide animation.
* argument callback function - Called when the animation is done. * @param {Function} callback Called when the animation is done.
* argument options object - Optional settings for the hide method. * @param {object} [options] Optional settings for the hide method.
*/ */
hide: function (speed, callback, options) { hide: function (speed, callback, options) {
if (typeof callback === "object") { if (typeof callback === "object") {
@ -405,12 +411,12 @@ var Module = Class.extend({
); );
}, },
/* showModule(module, speed, callback) /**
* Show this module. * Show this module.
* *
* argument speed Number - The speed of the show animation. * @param {number} speed The speed of the show animation.
* argument callback function - Called when the animation is done. * @param {Function} callback Called when the animation is done.
* argument options object - Optional settings for the hide method. * @param {object} [options] Optional settings for the show method.
*/ */
show: function (speed, callback, options) { show: function (speed, callback, options) {
if (typeof callback === "object") { if (typeof callback === "object") {
@ -451,11 +457,27 @@ Module.create = function (name) {
return new ModuleClass(); return new ModuleClass();
}; };
/* cmpVersions(a,b) Module.register = function (name, moduleDefinition) {
if (moduleDefinition.requiresVersion) {
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version);
if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) {
Log.log("Version is ok!");
} else {
Log.log("Version is incorrect. Skip module: '" + name + "'");
return;
}
}
Log.log("Module registered: " + name);
Module.definitions[name] = moduleDefinition;
};
/**
* Compare two semantic version numbers and return the difference. * Compare two semantic version numbers and return the difference.
* *
* argument a string - Version number a. * @param {string} a Version number a.
* argument a string - Version number b. * @param {string} b Version number b.
* @returns {number} A positive number if a is larger than b, a negative
* number if a is smaller and 0 if they are the same
*/ */
function cmpVersions(a, b) { function cmpVersions(a, b) {
var i, diff; var i, diff;
@ -472,17 +494,3 @@ function cmpVersions(a, b) {
} }
return segmentsA.length - segmentsB.length; return segmentsA.length - segmentsB.length;
} }
Module.register = function (name, moduleDefinition) {
if (moduleDefinition.requiresVersion) {
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version);
if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) {
Log.log("Version is ok!");
} else {
Log.log("Version is incorrect. Skip module: '" + name + "'");
return;
}
}
Log.log("Module registered: " + name);
Module.definitions[name] = moduleDefinition;
};

View File

@ -7,11 +7,11 @@
* MIT Licensed. * MIT Licensed.
*/ */
var Translator = (function () { var Translator = (function () {
/* loadJSON(file, callback) /**
* Load a JSON file via XHR. * Load a JSON file via XHR.
* *
* argument file string - Path of the file we want to load. * @param {string} file Path of the file we want to load.
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
function loadJSON(file, callback) { function loadJSON(file, callback) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@ -31,20 +31,27 @@ var Translator = (function () {
translations: {}, translations: {},
translationsFallback: {}, translationsFallback: {},
/* translate(module, key, variables) /**
* Load a translation for a given key for a given module. * Load a translation for a given key for a given module.
* *
* argument module Module - The module to load the translation for. * @param {Module} module The module to load the translation for.
* argument key string - The key of the text to translate. * @param {string} key The key of the text to translate.
* argument variables - The variables to use within the translation template (optional) * @param {object} variables The variables to use within the translation template (optional)
* @returns {string} the translated key
*/ */
translate: function (module, key, variables) { translate: function (module, key, variables) {
variables = variables || {}; //Empty object by default variables = variables || {}; //Empty object by default
// Combines template and variables like: /**
// template: "Please wait for {timeToWait} before continuing with {work}." * Combines template and variables like:
// variables: {timeToWait: "2 hours", work: "painting"} * template: "Please wait for {timeToWait} before continuing with {work}."
// to: "Please wait for 2 hours before continuing with painting." * variables: {timeToWait: "2 hours", work: "painting"}
* to: "Please wait for 2 hours before continuing with painting."
*
* @param {string} template Text with placeholder
* @param {object} variables Variables for the placeholder
* @returns {string} the template filled with the variables
*/
function createStringFromTemplate(template, variables) { function createStringFromTemplate(template, variables) {
if (Object.prototype.toString.call(template) !== "[object String]") { if (Object.prototype.toString.call(template) !== "[object String]") {
return template; return template;
@ -80,13 +87,13 @@ var Translator = (function () {
return key; return key;
}, },
/* load(module, file, isFallback, callback) /**
* Load a translation file (json) and remember the data. * Load a translation file (json) and remember the data.
* *
* argument module Module - The module to load the translation file for. * @param {Module} module The module to load the translation file for.
* argument file string - Path of the file we want to load. * @param {string} file Path of the file we want to load.
* argument isFallback boolean - Flag to indicate fallback translations. * @param {boolean} isFallback Flag to indicate fallback translations.
* argument callback function - Function called when done. * @param {Function} callback Function called when done.
*/ */
load: function (module, file, isFallback, callback) { load: function (module, file, isFallback, callback) {
if (!isFallback) { if (!isFallback) {
@ -110,10 +117,10 @@ var Translator = (function () {
} }
}, },
/* loadCoreTranslations(lang) /**
* Load the core translations. * Load the core translations.
* *
* argument lang String - The language identifier of the core language. * @param {string} lang The language identifier of the core language.
*/ */
loadCoreTranslations: function (lang) { loadCoreTranslations: function (lang) {
var self = this; var self = this;
@ -130,7 +137,7 @@ var Translator = (function () {
self.loadCoreTranslationsFallback(); self.loadCoreTranslationsFallback();
}, },
/* loadCoreTranslationsFallback() /**
* Load the core translations fallback. * Load the core translations fallback.
* The first language defined in translations.js will be used. * The first language defined in translations.js will be used.
*/ */

View File

@ -10,7 +10,8 @@ var Utils = {
colors: { colors: {
warn: colors.yellow, warn: colors.yellow,
error: colors.red, error: colors.red,
info: colors.blue info: colors.blue,
pass: colors.green
} }
}; };

View File

@ -12,7 +12,11 @@
*/ */
(function (window) { (function (window) {
/** /**
* extend obj function * Extend one object with another one
*
* @param {object} a The object to extend
* @param {object} b The object which extends the other, overwrites existing keys
* @returns {object} The merged object
*/ */
function extend(a, b) { function extend(a, b) {
for (let key in b) { for (let key in b) {
@ -24,7 +28,10 @@
} }
/** /**
* NotificationFx function * NotificationFx constructor
*
* @param {object} options The configuration options
* @class
*/ */
function NotificationFx(options) { function NotificationFx(options) {
this.options = extend({}, this.options); this.options = extend({}, this.options);
@ -66,8 +73,7 @@
}; };
/** /**
* init function * Initialize and cache some vars
* initialize and cache some vars
*/ */
NotificationFx.prototype._init = function () { NotificationFx.prototype._init = function () {
// create HTML structure // create HTML structure
@ -95,7 +101,7 @@
}; };
/** /**
* init events * Init events
*/ */
NotificationFx.prototype._initEvents = function () { NotificationFx.prototype._initEvents = function () {
// dismiss notification by tapping on it if someone has a touchscreen // dismiss notification by tapping on it if someone has a touchscreen
@ -105,7 +111,7 @@
}; };
/** /**
* show the notification * Show the notification
*/ */
NotificationFx.prototype.show = function () { NotificationFx.prototype.show = function () {
this.active = true; this.active = true;
@ -115,7 +121,7 @@
}; };
/** /**
* dismiss the notification * Dismiss the notification
*/ */
NotificationFx.prototype.dismiss = function () { NotificationFx.prototype.dismiss = function () {
this.active = false; this.active = false;
@ -144,7 +150,7 @@
}; };
/** /**
* add to global namespace * Add to global namespace
*/ */
window.NotificationFx = NotificationFx; window.NotificationFx = NotificationFx;
})(window); })(window);

View File

@ -416,7 +416,7 @@ Module.register("calendar", {
* it will a localeSpecification object with the system locale time format. * it will a localeSpecification object with the system locale time format.
* *
* @param {number} timeFormat Specifies either 12 or 24 hour time format * @param {number} timeFormat Specifies either 12 or 24 hour time format
* @returns {moment.LocaleSpecification} * @returns {moment.LocaleSpecification} formatted time
*/ */
getLocaleSpecification: function (timeFormat) { getLocaleSpecification: function (timeFormat) {
switch (timeFormat) { switch (timeFormat) {
@ -432,12 +432,11 @@ Module.register("calendar", {
} }
}, },
/* hasCalendarURL(url) /**
* Check if this config contains the calendar url. * Checks if this config contains the calendar url.
* *
* argument url string - Url to look for. * @param {string} url The calendar url
* * @returns {boolean} True if the calendar config contains the url, False otherwise
* return bool - Has calendar url
*/ */
hasCalendarURL: function (url) { hasCalendarURL: function (url) {
for (var c in this.config.calendars) { for (var c in this.config.calendars) {
@ -450,10 +449,10 @@ Module.register("calendar", {
return false; return false;
}, },
/* createEventList() /**
* Creates the sorted list of all events. * Creates the sorted list of all events.
* *
* return array - Array with events. * @returns {object[]} Array with events.
*/ */
createEventList: function () { createEventList: function () {
var events = []; var events = [];
@ -534,10 +533,12 @@ Module.register("calendar", {
return false; return false;
}, },
/* createEventList(url) /**
* Requests node helper to add calendar url. * Requests node helper to add calendar url.
* *
* argument url string - Url to add. * @param {string} url The calendar url to add
* @param {object} auth The authentication method and credentials
* @param {object} calendarConfig The config of the specific calendar
*/ */
addCalendar: function (url, auth, calendarConfig) { addCalendar: function (url, auth, calendarConfig) {
this.sendSocketNotification("ADD_CALENDAR", { this.sendSocketNotification("ADD_CALENDAR", {
@ -556,12 +557,10 @@ Module.register("calendar", {
}, },
/** /**
* symbolsForEvent(event)
* Retrieves the symbols for a specific event. * Retrieves the symbols for a specific event.
* *
* argument event object - Event to look for. * @param {object} event Event to look for.
* * @returns {string[]} The symbols
* return array - The Symbols
*/ */
symbolsForEvent: function (event) { symbolsForEvent: function (event) {
let symbols = this.getCalendarPropertyAsArray(event.url, "symbol", this.config.defaultSymbol); let symbols = this.getCalendarPropertyAsArray(event.url, "symbol", this.config.defaultSymbol);
@ -586,82 +585,72 @@ Module.register("calendar", {
}, },
/** /**
* symbolClassForUrl(url) * Retrieves the symbolClass for a specific calendar url.
* Retrieves the symbolClass for a specific url.
* *
* @param url string - Url to look for. * @param {string} url The calendar url
* * @returns {string} The class to be used for the symbols of the calendar
* @returns string
*/ */
symbolClassForUrl: function (url) { symbolClassForUrl: function (url) {
return this.getCalendarProperty(url, "symbolClass", ""); return this.getCalendarProperty(url, "symbolClass", "");
}, },
/** /**
* titleClassForUrl(url) * Retrieves the titleClass for a specific calendar url.
* Retrieves the titleClass for a specific url.
* *
* @param url string - Url to look for. * @param {string} url The calendar url
* * @returns {string} The class to be used for the title of the calendar
* @returns string
*/ */
titleClassForUrl: function (url) { titleClassForUrl: function (url) {
return this.getCalendarProperty(url, "titleClass", ""); return this.getCalendarProperty(url, "titleClass", "");
}, },
/** /**
* timeClassForUrl(url) * Retrieves the timeClass for a specific calendar url.
* Retrieves the timeClass for a specific url.
* *
* @param url string - Url to look for. * @param {string} url The calendar url
* * @returns {string} The class to be used for the time of the calendar
* @returns string
*/ */
timeClassForUrl: function (url) { timeClassForUrl: function (url) {
return this.getCalendarProperty(url, "timeClass", ""); return this.getCalendarProperty(url, "timeClass", "");
}, },
/* calendarNameForUrl(url) /**
* Retrieves the calendar name for a specific url. * Retrieves the calendar name for a specific calendar url.
* *
* argument url string - Url to look for. * @param {string} url The calendar url
* * @returns {string} The name of the calendar
* return string - The name of the calendar
*/ */
calendarNameForUrl: function (url) { calendarNameForUrl: function (url) {
return this.getCalendarProperty(url, "name", ""); return this.getCalendarProperty(url, "name", "");
}, },
/* colorForUrl(url) /**
* Retrieves the color for a specific url. * Retrieves the color for a specific calendar url.
* *
* argument url string - Url to look for. * @param {string} url The calendar url
* * @returns {string} The color
* return string - The Color
*/ */
colorForUrl: function (url) { colorForUrl: function (url) {
return this.getCalendarProperty(url, "color", "#fff"); return this.getCalendarProperty(url, "color", "#fff");
}, },
/* countTitleForUrl(url) /**
* Retrieves the name for a specific url. * Retrieves the count title for a specific calendar url.
* *
* argument url string - Url to look for. * @param {string} url The calendar url
* * @returns {string} The title
* return string - The Symbol
*/ */
countTitleForUrl: function (url) { countTitleForUrl: function (url) {
return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle); return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle);
}, },
/* getCalendarProperty(url, property, defaultValue) /**
* Helper method to retrieve the property for a specific url. * Helper method to retrieve the property for a specific calendar url.
* *
* argument url string - Url to look for. * @param {string} url The calendar url
* argument property string - Property to look for. * @param {string} property The property to look for
* argument defaultValue string - Value if property is not found. * @param {string} defaultValue The value if the property is not found
* * @returns {*} The property
* return string - The Property
*/ */
getCalendarProperty: function (url, property, defaultValue) { getCalendarProperty: function (url, property, defaultValue) {
for (var c in this.config.calendars) { for (var c in this.config.calendars) {
@ -737,22 +726,27 @@ Module.register("calendar", {
} }
}, },
/* capFirst(string) /**
* Capitalize the first letter of a string * Capitalize the first letter of a string
* Return capitalized string *
* @param {string} string The string to capitalize
* @returns {string} The capitalized string
*/ */
capFirst: function (string) { capFirst: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
}, },
/* titleTransform(title) /**
* Transforms the title of an event for usage. * Transforms the title of an event for usage.
* Replaces parts of the text as defined in config.titleReplace. * Replaces parts of the text as defined in config.titleReplace.
* Shortens title based on config.maxTitleLength and config.wrapEvents * Shortens title based on config.maxTitleLength and config.wrapEvents
* *
* argument title string - The title to transform. * @param {string} title The title to transform.
* * @param {object} titleReplace Pairs of strings to be replaced in the title
* return string - The transformed title. * @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
* @param {number} maxTitleLength The max length of the string
* @param {number} maxTitleLines The max number of vertical lines before cutting event title
* @returns {string} The transformed title.
*/ */
titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) { titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) {
for (var needle in titleReplace) { for (var needle in titleReplace) {
@ -771,7 +765,7 @@ Module.register("calendar", {
return title; return title;
}, },
/* broadcastEvents() /**
* Broadcasts the events to all other modules for reuse. * Broadcasts the events to all other modules for reuse.
* The all events available in one array, sorted on startdate. * The all events available in one array, sorted on startdate.
*/ */

View File

@ -6,9 +6,27 @@
*/ */
const Log = require("../../../js/logger.js"); const Log = require("../../../js/logger.js");
const ical = require("ical"); const ical = require("ical");
const moment = require("moment");
const request = require("request"); const request = require("request");
/**
* Moment date
*
* @external Moment
* @see {@link http://momentjs.com}
*/
const moment = require("moment");
/**
*
* @param {string} url The url of the calendar to fetch
* @param {number} reloadInterval Time in ms the calendar is fetched again
* @param {string[]} excludedEvents An array of words / phrases from event titles that will be excluded from being shown.
* @param {number} maximumEntries The maximum number of events fetched.
* @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
* @class
*/
const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) { const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) {
const self = this; const self = this;
@ -18,7 +36,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
let fetchFailedCallback = function () {}; let fetchFailedCallback = function () {};
let eventsReceivedCallback = function () {}; let eventsReceivedCallback = function () {};
/* fetchCalendar() /**
* Initiates calendar fetch. * Initiates calendar fetch.
*/ */
const fetchCalendar = function () { const fetchCalendar = function () {
@ -184,8 +202,16 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
// For recurring events, get the set of start dates that fall within the range // For recurring events, get the set of start dates that fall within the range
// of dates we're looking for. // of dates we're looking for.
// kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time // kblankenship1989 - to fix issue #1798, converting all dates to locale time first, then converting back to UTC time
const pastLocal = pastMoment.subtract(past.getTimezoneOffset(), "minutes").toDate(); let pastLocal = 0;
const futureLocal = futureMoment.subtract(future.getTimezoneOffset(), "minutes").toDate(); let futureLocal = 0;
if (isFullDayEvent(event)) {
// if full day event, only use the date part of the ranges
pastLocal = pastMoment.toDate();
futureLocal = futureMoment.toDate();
} else {
pastLocal = pastMoment.subtract(past.getTimezoneOffset(), "minutes").toDate();
futureLocal = futureMoment.subtract(future.getTimezoneOffset(), "minutes").toDate();
}
const dates = rule.between(pastLocal, futureLocal, true, limitFunction); const dates = rule.between(pastLocal, futureLocal, true, limitFunction);
// The "dates" array contains the set of dates within our desired date range range that are valid // The "dates" array contains the set of dates within our desired date range range that are valid
@ -214,6 +240,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
const dateKey = date.toISOString().substring(0, 10); const dateKey = date.toISOString().substring(0, 10);
let curEvent = event; let curEvent = event;
let showRecurrence = true; let showRecurrence = true;
let duration = 0;
startDate = moment(date); startDate = moment(date);
@ -325,7 +352,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
}); });
}; };
/* scheduleTimer() /**
* Schedule the timer for the next update. * Schedule the timer for the next update.
*/ */
const scheduleTimer = function () { const scheduleTimer = function () {
@ -335,12 +362,11 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
}, reloadInterval); }, reloadInterval);
}; };
/* isFullDayEvent(event) /**
* Checks if an event is a fullday event. * Checks if an event is a fullday event.
* *
* argument event object - The event object to check. * @param {object} event The event object to check.
* * @returns {boolean} True if the event is a fullday event, false otherwise
* return bool - The event is a fullday event.
*/ */
const isFullDayEvent = function (event) { const isFullDayEvent = function (event) {
if (event.start.length === 8 || event.start.dateOnly) { if (event.start.length === 8 || event.start.dateOnly) {
@ -358,14 +384,13 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
return false; return false;
}; };
/* timeFilterApplies() /**
* Determines if the user defined time filter should apply * Determines if the user defined time filter should apply
* *
* argument now Date - Date object using previously created object for consistency * @param {Date} now Date object using previously created object for consistency
* argument endDate Moment - Moment object representing the event end date * @param {Moment} endDate Moment object representing the event end date
* argument filter string - The time to subtract from the end date to determine if an event should be shown * @param {string} filter The time to subtract from the end date to determine if an event should be shown
* * @returns {boolean} True if the event should be filtered out, false otherwise
* return bool - The event should be filtered out
*/ */
const timeFilterApplies = function (now, endDate, filter) { const timeFilterApplies = function (now, endDate, filter) {
if (filter) { if (filter) {
@ -380,12 +405,11 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
return false; return false;
}; };
/* getTitleFromEvent(event) /**
* Gets the title from the event. * Gets the title from the event.
* *
* argument event object - The event object to check. * @param {object} event The event object to check.
* * @returns {string} The title of the event, or "Event" if no title is found.
* return string - The title of the event, or "Event" if no title is found.
*/ */
const getTitleFromEvent = function (event) { const getTitleFromEvent = function (event) {
let title = "Event"; let title = "Event";
@ -416,14 +440,14 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
/* public methods */ /* public methods */
/* startFetch() /**
* Initiate fetchCalendar(); * Initiate fetchCalendar();
*/ */
this.startFetch = function () { this.startFetch = function () {
fetchCalendar(); fetchCalendar();
}; };
/* broadcastItems() /**
* Broadcast the existing events. * Broadcast the existing events.
*/ */
this.broadcastEvents = function () { this.broadcastEvents = function () {
@ -431,37 +455,37 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
eventsReceivedCallback(self); eventsReceivedCallback(self);
}; };
/* onReceive(callback) /**
* Sets the on success callback * Sets the on success callback
* *
* argument callback function - The on success callback. * @param {Function} callback The on success callback.
*/ */
this.onReceive = function (callback) { this.onReceive = function (callback) {
eventsReceivedCallback = callback; eventsReceivedCallback = callback;
}; };
/* onError(callback) /**
* Sets the on error callback * Sets the on error callback
* *
* argument callback function - The on error callback. * @param {Function} callback The on error callback.
*/ */
this.onError = function (callback) { this.onError = function (callback) {
fetchFailedCallback = callback; fetchFailedCallback = callback;
}; };
/* url() /**
* Returns the url of this fetcher. * Returns the url of this fetcher.
* *
* return string - The url of this fetcher. * @returns {string} The url of this fetcher.
*/ */
this.url = function () { this.url = function () {
return url; return url;
}; };
/* events() /**
* Returns current available events for this fetcher. * Returns current available events for this fetcher.
* *
* return array - The current available events for this fetcher. * @returns {object[]} The current available events for this fetcher.
*/ */
this.events = function () { this.events = function () {
return events; return events;

View File

@ -4,7 +4,6 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
const NodeHelper = require("node_helper"); const NodeHelper = require("node_helper");
const validUrl = require("valid-url"); const validUrl = require("valid-url");
const CalendarFetcher = require("./calendarfetcher.js"); const CalendarFetcher = require("./calendarfetcher.js");
@ -24,12 +23,18 @@ module.exports = NodeHelper.create({
} }
}, },
/* createFetcher(url, reloadInterval) /**
* Creates a fetcher for a new url if it doesn't exist yet. * Creates a fetcher for a new url if it doesn't exist yet.
* Otherwise it reuses the existing one. * Otherwise it reuses the existing one.
* *
* attribute url string - URL of the news feed. * @param {string} url The url of the calendar
* attribute reloadInterval number - Reload interval in milliseconds. * @param {number} fetchInterval How often does the calendar needs to be fetched in ms
* @param {string[]} excludedEvents An array of words / phrases from event titles that will be excluded from being shown.
* @param {number} maximumEntries The maximum number of events fetched.
* @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 {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, identifier) {
var self = this; var self = this;

View File

@ -152,6 +152,13 @@ Module.register("clock", {
timeWrapper.appendChild(periodWrapper); timeWrapper.appendChild(periodWrapper);
} }
/**
* Format the time according to the config
*
* @param {object} config The config of the module
* @param {object} time time to format
* @returns {string} The formatted time string
*/
function formatTime(config, time) { function formatTime(config, time) {
var formatString = hourSymbol + ":mm"; var formatString = hourSymbol + ":mm";
if (config.showPeriod && config.timeFormat !== 24) { if (config.showPeriod && config.timeFormat !== 24) {
@ -159,6 +166,7 @@ Module.register("clock", {
} }
return moment(time).format(formatString); return moment(time).format(formatString);
} }
if (this.config.showSunTimes) { if (this.config.showSunTimes) {
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon); const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset); const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);

View File

@ -37,6 +37,8 @@ Module.register("currentweather", {
weatherEndpoint: "weather", weatherEndpoint: "weather",
appendLocationNameToHeader: true, appendLocationNameToHeader: true,
useLocationAsHeader: false,
calendarClass: "calendar", calendarClass: "calendar",
tableClass: "large", tableClass: "large",
@ -267,15 +269,16 @@ Module.register("currentweather", {
// Override getHeader method. // Override getHeader method.
getHeader: function () { getHeader: function () {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined) {
return this.data.header + " " + this.fetchedLocationName;
}
if (this.config.useLocationAsHeader && this.config.location !== false) { if (this.config.useLocationAsHeader && this.config.location !== false) {
return this.config.location; return this.config.location;
} }
return this.data.header; if (this.config.appendLocationNameToHeader) {
if (this.data.header) return this.data.header + " " + this.fetchedLocationName;
else return this.fetchedLocationName;
}
return this.data.header ? this.data.header : "";
}, },
// Override notification handler. // Override notification handler.

View File

@ -205,8 +205,8 @@ Module.register("newsfeed", {
return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href; return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
}, },
/* registerFeeds() /**
* registers the feeds to be used by the backend. * Registers the feeds to be used by the backend.
*/ */
registerFeeds: function () { registerFeeds: function () {
for (var f in this.config.feeds) { for (var f in this.config.feeds) {
@ -218,10 +218,10 @@ Module.register("newsfeed", {
} }
}, },
/* generateFeed() /**
* Generate an ordered list of items for this configured module. * Generate an ordered list of items for this configured module.
* *
* attribute feeds object - An object with feeds returned by the node helper. * @param {object} feeds An object with feeds returned by the node helper.
*/ */
generateFeed: function (feeds) { generateFeed: function (feeds) {
var newsItems = []; var newsItems = [];
@ -274,12 +274,11 @@ Module.register("newsfeed", {
this.newsItems = newsItems; this.newsItems = newsItems;
}, },
/* subscribedToFeed(feedUrl) /**
* Check if this module is configured to show this feed. * Check if this module is configured to show this feed.
* *
* attribute feedUrl string - Url of the feed to check. * @param {string} feedUrl Url of the feed to check.
* * @returns {boolean} True if it is subscribed, false otherwise
* returns bool
*/ */
subscribedToFeed: function (feedUrl) { subscribedToFeed: function (feedUrl) {
for (var f in this.config.feeds) { for (var f in this.config.feeds) {
@ -291,12 +290,11 @@ Module.register("newsfeed", {
return false; return false;
}, },
/* titleForFeed(feedUrl) /**
* Returns title for a specific feed Url. * Returns title for the specific feed url.
* *
* attribute feedUrl string - Url of the feed to check. * @param {string} feedUrl Url of the feed
* * @returns {string} The title of the feed
* returns string
*/ */
titleForFeed: function (feedUrl) { titleForFeed: function (feedUrl) {
for (var f in this.config.feeds) { for (var f in this.config.feeds) {
@ -308,7 +306,7 @@ Module.register("newsfeed", {
return ""; return "";
}, },
/* scheduleUpdateInterval() /**
* Schedule visual update. * Schedule visual update.
*/ */
scheduleUpdateInterval: function () { scheduleUpdateInterval: function () {

View File

@ -9,12 +9,14 @@ const FeedMe = require("feedme");
const request = require("request"); const request = require("request");
const iconv = require("iconv-lite"); const iconv = require("iconv-lite");
/* Fetcher /**
* Responsible for requesting an update on the set interval and broadcasting the data. * Responsible for requesting an update on the set interval and broadcasting the data.
* *
* attribute url string - URL of the news feed. * @param {string} url URL of the news feed.
* attribute reloadInterval number - Reload interval in milliseconds. * @param {number} reloadInterval Reload interval in milliseconds.
* attribute logFeedWarnings boolean - Log warnings when there is an error parsing a news article. * @param {string} encoding Encoding of the feed.
* @param {boolean} logFeedWarnings If true log warnings when there is an error parsing a news article.
* @class
*/ */
const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings) { const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
const self = this; const self = this;
@ -31,7 +33,7 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
/* private methods */ /* private methods */
/* fetchNews() /**
* Request the new items. * Request the new items.
*/ */
const fetchNews = function () { const fetchNews = function () {
@ -95,7 +97,7 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
.pipe(parser); .pipe(parser);
}; };
/* scheduleTimer() /**
* Schedule the timer for the next update. * Schedule the timer for the next update.
*/ */
const scheduleTimer = function () { const scheduleTimer = function () {
@ -107,10 +109,10 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
/* public methods */ /* public methods */
/* setReloadInterval() /**
* Update the reload interval, but only if we need to increase the speed. * Update the reload interval, but only if we need to increase the speed.
* *
* attribute interval number - Interval for the update in milliseconds. * @param {number} interval Interval for the update in milliseconds.
*/ */
this.setReloadInterval = function (interval) { this.setReloadInterval = function (interval) {
if (interval > 1000 && interval < reloadInterval) { if (interval > 1000 && interval < reloadInterval) {
@ -118,14 +120,14 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
} }
}; };
/* startFetch() /**
* Initiate fetchNews(); * Initiate fetchNews();
*/ */
this.startFetch = function () { this.startFetch = function () {
fetchNews(); fetchNews();
}; };
/* broadcastItems() /**
* Broadcast the existing items. * Broadcast the existing items.
*/ */
this.broadcastItems = function () { this.broadcastItems = function () {

View File

@ -24,12 +24,12 @@ module.exports = NodeHelper.create({
} }
}, },
/* createFetcher(feed, config) /**
* Creates a fetcher for a new feed if it doesn't exist yet. * Creates a fetcher for a new feed if it doesn't exist yet.
* Otherwise it reuses the existing one. * Otherwise it reuses the existing one.
* *
* attribute feed object - A feed object. * @param {object} feed The feed object.
* attribute config object - A configuration object containing reload interval in milliseconds. * @param {object} config The configuration object.
*/ */
createFetcher: function (feed, config) { createFetcher: function (feed, config) {
const url = feed.url || ""; const url = feed.url || "";
@ -68,7 +68,7 @@ module.exports = NodeHelper.create({
fetcher.startFetch(); fetcher.startFetch();
}, },
/* broadcastFeeds() /**
* Creates an object with all feed items of the different registered feeds, * Creates an object with all feed items of the different registered feeds,
* and broadcasts these using sendSocketNotification. * and broadcasts these using sendSocketNotification.
*/ */

View File

@ -72,7 +72,7 @@ module.exports = NodeHelper.create({
performFetch: function () { performFetch: function () {
var self = this; var self = this;
simpleGits.forEach((sg) => { simpleGits.forEach((sg) => {
sg.git.fetch().status((err, data) => { sg.git.fetch(["--dry-run"]).status((err, data) => {
data.module = sg.module; data.module = sg.module;
if (!err) { if (!err) {
sg.git.log({ "-1": null }, (err, data2) => { sg.git.log({ "-1": null }, (err, data2) => {

View File

@ -1,5 +1,5 @@
# Weather Module # Weather Module
This module aims to be the replacement for the current `currentweather` and `weatherforcast` modules. The module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fullfil both purposes. This module aims to be the replacement for the current `currentweather` and `weatherforcast` modules. The module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fulfill both purposes.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html). For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html).

View File

@ -76,11 +76,12 @@ Module.register("weather", {
// Override getHeader method. // Override getHeader method.
getHeader: function () { getHeader: function () {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined && this.weatherProvider) { if (this.config.appendLocationNameToHeader && this.weatherProvider) {
return this.data.header + " " + this.weatherProvider.fetchedLocation(); if (this.data.header) return this.data.header + " " + this.weatherProvider.fetchedLocation();
else return this.weatherProvider.fetchedLocation();
} }
return this.data.header; return this.data.header ? this.data.header : "";
}, },
// Start the weather module. // Start the weather module.

View File

@ -12,7 +12,7 @@ var WeatherProvider = Class.extend({
// Weather Provider Properties // Weather Provider Properties
providerName: null, providerName: null,
// The following properties have accestor methods. // The following properties have accessor methods.
// Try to not access them directly. // Try to not access them directly.
currentWeatherObject: null, currentWeatherObject: null,
weatherForecastArray: null, weatherForecastArray: null,
@ -136,6 +136,9 @@ WeatherProvider.providers = [];
/** /**
* Static method to register a new weather provider. * Static method to register a new weather provider.
*
* @param {string} providerIdentifier The name of the weather provider
* @param {object} providerDetails The details of the weather provider
*/ */
WeatherProvider.register = function (providerIdentifier, providerDetails) { WeatherProvider.register = function (providerIdentifier, providerDetails) {
WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails); WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails);
@ -143,6 +146,10 @@ WeatherProvider.register = function (providerIdentifier, providerDetails) {
/** /**
* Static method to initialize a new weather provider. * Static method to initialize a new weather provider.
*
* @param {string} providerIdentifier The name of the weather provider
* @param {object} delegate The weather module
* @returns {object} The new weather provider
*/ */
WeatherProvider.initialize = function (providerIdentifier, delegate) { WeatherProvider.initialize = function (providerIdentifier, delegate) {
providerIdentifier = providerIdentifier.toLowerCase(); providerIdentifier = providerIdentifier.toLowerCase();

View File

@ -103,7 +103,7 @@ Module.register("weatherforecast", {
getDom: function () { getDom: function () {
var wrapper = document.createElement("div"); var wrapper = document.createElement("div");
if (this.config.appid === "") { if (this.config.appid === "" || this.config.appid === "YOUR_OPENWEATHER_API_KEY") {
wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + "."; wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
wrapper.className = "dimmed light small"; wrapper.className = "dimmed light small";
return wrapper; return wrapper;
@ -206,10 +206,11 @@ Module.register("weatherforecast", {
// Override getHeader method. // Override getHeader method.
getHeader: function () { getHeader: function () {
if (this.config.appendLocationNameToHeader) { if (this.config.appendLocationNameToHeader) {
return this.data.header + " " + this.fetchedLocationName; if (this.data.header) return this.data.header + " " + this.fetchedLocationName;
else return this.fetchedLocationName;
} }
return this.data.header; return this.data.header ? this.data.header : "";
}, },
// Override notification handler. // Override notification handler.

128
package-lock.json generated
View File

@ -601,24 +601,6 @@
"url-template": "^2.0.8" "url-template": "^2.0.8"
} }
}, },
"@prantlf/jsonlint": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/@prantlf/jsonlint/-/jsonlint-10.2.0.tgz",
"integrity": "sha512-KMFfds0peWLLfCu3bhClTiEN0tdj/Z86QJvn1awKHws6r+Sx6T3a44Eadz6OvqN6ZpsRkqaRpZxqddvvDAdDZQ==",
"dev": true,
"requires": {
"ajv": "6.10.2",
"commander": "4.0.1"
},
"dependencies": {
"commander": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.0.1.tgz",
"integrity": "sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA==",
"dev": true
}
}
},
"@stylelint/postcss-css-in-js": { "@stylelint/postcss-css-in-js": {
"version": "0.37.1", "version": "0.37.1",
"resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz", "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz",
@ -1480,6 +1462,12 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true "dev": true
}, },
"comment-parser": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.5.tgz",
"integrity": "sha512-iH9YA35ccw94nx5244GVkpyC9eVTsL71jZz6iz5w6RIf79JLF2AsXHXq9p6Oaohyl3sx5qSMnGsWUDFIAfWL4w==",
"dev": true
},
"commondir": { "commondir": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@ -1739,12 +1727,6 @@
"cssom": "0.3.x" "cssom": "0.3.x"
} }
}, },
"current-week-number": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/current-week-number/-/current-week-number-1.0.7.tgz",
"integrity": "sha1-VnJ4rrX+WN7LFQuayGT5Pc5O2XI=",
"dev": true
},
"currently-unhandled": { "currently-unhandled": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@ -2378,9 +2360,9 @@
} }
}, },
"eslint": { "eslint": {
"version": "7.4.0", "version": "7.6.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.4.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.6.0.tgz",
"integrity": "sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g==", "integrity": "sha512-QlAManNtqr7sozWm5TF4wIH9gmUm2hE3vNRUvyoYAa4y1l5/jxD/PQStEjBMQtCqZmSep8UxrcecI60hOpe61w==",
"requires": { "requires": {
"@babel/code-frame": "^7.0.0", "@babel/code-frame": "^7.0.0",
"ajv": "^6.10.0", "ajv": "^6.10.0",
@ -2390,9 +2372,9 @@
"doctrine": "^3.0.0", "doctrine": "^3.0.0",
"enquirer": "^2.3.5", "enquirer": "^2.3.5",
"eslint-scope": "^5.1.0", "eslint-scope": "^5.1.0",
"eslint-utils": "^2.0.0", "eslint-utils": "^2.1.0",
"eslint-visitor-keys": "^1.2.0", "eslint-visitor-keys": "^1.3.0",
"espree": "^7.1.0", "espree": "^7.2.0",
"esquery": "^1.2.0", "esquery": "^1.2.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"file-entry-cache": "^5.0.1", "file-entry-cache": "^5.0.1",
@ -2406,7 +2388,7 @@
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1", "levn": "^0.4.1",
"lodash": "^4.17.14", "lodash": "^4.17.19",
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"optionator": "^0.9.1", "optionator": "^0.9.1",
@ -2538,9 +2520,9 @@
} }
}, },
"strip-json-comments": { "strip-json-comments": {
"version": "3.1.0", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==" "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
}, },
"supports-color": { "supports-color": {
"version": "7.1.0", "version": "7.1.0",
@ -2585,6 +2567,48 @@
} }
} }
}, },
"eslint-plugin-jsdoc": {
"version": "30.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.1.0.tgz",
"integrity": "sha512-eMsX+TMW6ycgXwxqU9xqfts2/e7cWCSGzk+gHgDvqaITyMJr8AcHTdd4pAMjpnOh0cd16lPZv+/R5LbQ4uVHQA==",
"dev": true,
"requires": {
"comment-parser": "^0.7.5",
"debug": "^4.1.1",
"jsdoctypeparser": "^8.0.0",
"lodash": "^4.17.15",
"regextras": "^0.7.1",
"semver": "^7.3.2",
"spdx-expression-parse": "^3.0.1"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
},
"spdx-expression-parse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"dev": true,
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
}
}
},
"eslint-plugin-prettier": { "eslint-plugin-prettier": {
"version": "3.1.4", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz",
@ -2617,13 +2641,13 @@
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ=="
}, },
"espree": { "espree": {
"version": "7.1.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-7.2.0.tgz",
"integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", "integrity": "sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g==",
"requires": { "requires": {
"acorn": "^7.2.0", "acorn": "^7.3.1",
"acorn-jsx": "^5.2.0", "acorn-jsx": "^5.2.0",
"eslint-visitor-keys": "^1.2.0" "eslint-visitor-keys": "^1.3.0"
} }
}, },
"esprima": { "esprima": {
@ -3718,11 +3742,11 @@
} }
}, },
"iconv-lite": { "iconv-lite": {
"version": "0.5.1", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
"integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
"requires": { "requires": {
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3.0.0"
} }
}, },
"ieee754": { "ieee754": {
@ -4188,6 +4212,12 @@
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
}, },
"jsdoctypeparser": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-8.0.0.tgz",
"integrity": "sha512-eLCs6s4JqN8TjFJfgdiLHRvogLhOAJz+5RIA2FtoMe6ZDyuvghvppnlIToqAEnVbxRqLMrfnNXpW8FpmR6IMBw==",
"dev": true
},
"jsdom": { "jsdom": {
"version": "11.12.0", "version": "11.12.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz",
@ -5210,9 +5240,9 @@
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q=="
}, },
"moment": { "moment": {
"version": "2.24.0", "version": "2.27.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
}, },
"mri": { "mri": {
"version": "1.1.5", "version": "1.1.5",
@ -6757,6 +6787,12 @@
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==" "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q=="
}, },
"regextras": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.1.tgz",
"integrity": "sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w==",
"dev": true
},
"release-zalgo": { "release-zalgo": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",

View File

@ -42,12 +42,11 @@
}, },
"homepage": "https://magicmirror.builders", "homepage": "https://magicmirror.builders",
"devDependencies": { "devDependencies": {
"@prantlf/jsonlint": "^10.2.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"current-week-number": "^1.0.7",
"danger": "^3.1.3", "danger": "^3.1.3",
"eslint-config-prettier": "^6.11.0", "eslint-config-prettier": "^6.11.0",
"eslint-plugin-jsdoc": "^30.1.0",
"eslint-plugin-prettier": "^3.1.4", "eslint-plugin-prettier": "^3.1.4",
"http-auth": "^3.2.3", "http-auth": "^3.2.3",
"husky": "^4.2.5", "husky": "^4.2.5",
@ -70,7 +69,7 @@
"dependencies": { "dependencies": {
"colors": "^1.1.2", "colors": "^1.1.2",
"console-stamp": "^0.2.9", "console-stamp": "^0.2.9",
"eslint": "^7.4.0", "eslint": "^7.6.0",
"express": "^4.16.2", "express": "^4.16.2",
"express-ipfilter": "^1.0.1", "express-ipfilter": "^1.0.1",
"feedme": "latest", "feedme": "latest",

View File

@ -0,0 +1,33 @@
/* Magic Mirror Test config for analog clock face
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true
}
},
modules: [
{
module: "clock",
position: "middle_center",
config: {
displayType: "analog",
analogFace: "face-006"
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@ -0,0 +1,42 @@
/* Magic Mirror Test config for display setters module using the helloworld module
*
* MIT Licensed.
*/
var config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
fullscreen: false,
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
},
modules: [
{
module: "helloworld",
position: "top_bar",
header: "test_header",
config: {
text: "Test Display Header"
}
},
{
module: "helloworld",
position: "bottom_bar",
config: {
text: "Test Hide Header"
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@ -20,7 +20,7 @@ var config = {
}, },
modules: modules:
// Using exotic content. This is why dont accept go to JSON configuration file // Using exotic content. This is why don't accept go to JSON configuration file
(function () { (function () {
var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"]; var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
var modules = Array(); var modules = Array();

View File

@ -1,4 +1,6 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
const expect = require("chai").expect;
const moment = require("moment");
const describe = global.describe; const describe = global.describe;
const it = global.it; const it = global.it;
@ -30,12 +32,12 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_24hr.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_24hr.js";
}); });
it("shows date with correct format", function () { it("should show the date in the correct format", function () {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex);
}); });
it("shows time in 24hr format", function () { it("should show the time in 24hr format", function () {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex);
}); });
@ -47,12 +49,12 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_12hr.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_12hr.js";
}); });
it("shows date with correct format", function () { it("should show the date in the correct format", function () {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex);
}); });
it("shows time in 12hr format", function () { it("should show the time in 12hr format", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex);
}); });
@ -64,7 +66,7 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showPeriodUpper.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showPeriodUpper.js";
}); });
it("shows 12hr time with upper case AM/PM", function () { it("should show 12hr time with upper case AM/PM", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex);
}); });
@ -76,7 +78,7 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_displaySeconds_false.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_displaySeconds_false.js";
}); });
it("shows 12hr time without seconds am/pm", function () { it("should show 12hr time without seconds am/pm", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/;
return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex);
}); });
@ -88,17 +90,28 @@ describe("Clock module", function () {
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showWeek.js"; process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showWeek.js";
}); });
it("shows week with correct format", function () { it("should show the week in the correct format", function () {
const weekRegex = /^Week [0-9]{1,2}$/; const weekRegex = /^Week [0-9]{1,2}$/;
return app.client.waitUntilWindowLoaded().getText(".clock .week").should.eventually.match(weekRegex); return app.client.waitUntilWindowLoaded().getText(".clock .week").should.eventually.match(weekRegex);
}); });
it("shows week with correct number of week of year", function () { it("should show the week with the correct number of week of year", function () {
it("FIXME: if the day is a sunday this not match"); const currentWeekNumber = moment().week();
// const currentWeekNumber = require("current-week-number")(); const weekToShow = "Week " + currentWeekNumber;
// const weekToShow = "Week " + currentWeekNumber; return app.client.waitUntilWindowLoaded().getText(".clock .week").should.eventually.equal(weekToShow);
// return app.client.waitUntilWindowLoaded() });
// .getText(".clock .week").should.eventually.equal(weekToShow); });
describe("with analog clock face enabled", function () {
before(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_analog.js";
});
it("should show the analog clock face", async () => {
await app.client.waitUntilWindowLoaded(10000);
const clock = await app.client.$$(".clockCircle");
return expect(clock.length).equals(1);
}); });
}); });
}); });

View File

@ -0,0 +1,41 @@
const helpers = require("./global-setup");
const describe = global.describe;
const it = global.it;
describe("Display of modules", function () {
helpers.setupTimeout(this);
var app = null;
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
describe("Using helloworld", function () {
before(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/display.js";
});
it("should show the test header", async () => {
await app.client.waitForExist("#module_0_helloworld", 10000);
return app.client.element("#module_0_helloworld .module-header").isVisible().should.eventually.equal(true).getText("#module_0_helloworld .module-header").should.eventually.equal("TEST_HEADER");
});
it("should show no header if no header text is specified", async () => {
await app.client.waitForExist("#module_1_helloworld", 10000);
return app.client.element("#module_1_helloworld .module-header").isVisible().should.eventually.equal(false);
});
});
});