diff --git a/.eslintrc.json b/.eslintrc.json index 6d6839d6..f24799a5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,6 @@ { - "extends": ["eslint:recommended", "plugin:prettier/recommended"], - "plugins": ["prettier"], + "extends": ["eslint:recommended", "plugin:prettier/recommended", "plugin:jsdoc/recommended"], + "plugins": ["prettier", "jsdoc"], "env": { "browser": true, "es6": true, diff --git a/.gitignore b/.gitignore index c3cf9bdb..08c2df0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # Various Node ignoramuses. - logs *.log npm-debug.log* @@ -13,9 +12,11 @@ build/Release /node_modules/**/* fonts/node_modules/**/* vendor/node_modules/**/* +!/tests/node_modules/**/* jspm_modules .npm .node_repl_history +.nyc_output/ # Visual Studio Code ignoramuses. .vscode/ @@ -53,7 +54,6 @@ Temporary Items .apdisk # Various Linux ignoramuses. - .fuse_hidden* .directory .Trash-* @@ -76,5 +76,3 @@ Temporary Items *.orig *.rej *.bak - -!/tests/node_modules/**/* diff --git a/.prettierignore b/.prettierignore index e4a77657..3b87fcd2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,4 @@ package-lock.json /config/**/* -/modules/default/calendar/vendor/ical.js/**/* /vendor/**/* !/vendor/vendor.js diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d1247a..b971805a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,38 @@ This project adheres to [Semantic Versioning](https://semver.org/). - 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.) + +_This release is scheduled to be released on 2020-10-01._ + +### 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`. +- Add lithuanian language. +- Added support in weatherforecast for OpenWeather onecall API. +- Added config option to calendar-icons for recurring- and fullday-events +- Added eslint-plugin for jsdoc comments + +### Updated + +- Change incorrect weather.js default properties. +- Cleaned up newsfeed module. +- Cleaned up jsdoc comments. + +### Deleted + +### Fixed + +- Fix backward compatibility issues for Safari < 11. [#1985](https://github.com/MichMich/MagicMirror/issues/1985) +- 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 logLevel being set before loading config. +- Fix incorrect namespace links in svg clockfaces. [#2072](https://github.com/MichMich/MagicMirror/issues/2072) +- Fix weather/providers/weathergov for API guidelines [#2045] + ## [2.12.0] - 2020-07-01 Special thanks to the following contributors: @AndreKoepke, @andrezibaia, @bryanzzhu, @chamakura, @DarthBrento, @Ekristoffe, @khassel, @Legion2, @ndom91, @radokristof, @rejas, @XBCreepinJesus & @ZoneMR. @@ -46,6 +78,7 @@ Special thanks to the following contributors: @AndreKoepke, @andrezibaia, @bryan - 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) - 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 diff --git a/clientonly/index.js b/clientonly/index.js index 7d1c4fe3..88454d93 100644 --- a/clientonly/index.js +++ b/clientonly/index.js @@ -4,10 +4,19 @@ (function () { 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() { - // 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) { var index = process.argv.indexOf(`--${key}`); var value = index > -1 ? process.argv[index + 1] : undefined; @@ -23,10 +32,17 @@ 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) { // Return new pending promise 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 request = lib.get(url, (response) => { 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) { if (message !== undefined && typeof message === "string") { console.log(message); diff --git a/js/app.js b/js/app.js index 7cf7dd2e..64844269 100644 --- a/js/app.js +++ b/js/app.js @@ -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"); }); -/* App - The core app. +/** + * The core app. + * + * @class */ var App = function () { var nodeHelpers = []; - /* loadConfig(callback) - * Loads the config file. combines it with the defaults, - * and runs the callback with the found config as argument. + /** + * Loads the config file. Combines it with the defaults, and runs the + * 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) { 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 deprecated = require(global.root_path + "/js/deprecated.js"); var deprecatedOptions = deprecated.configs; @@ -96,10 +105,11 @@ var App = function () { } }; - /* loadModule(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 elements = module.split("/"); @@ -144,10 +154,11 @@ var App = function () { } }; - /* loadModules(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) { Log.log("Loading module helpers ..."); @@ -169,11 +180,14 @@ var App = function () { loadNextModule(); }; - /* cmpVersions(a,b) + /** * Compare two semantic version numbers and return the difference. * - * argument a string - Version number a. - * argument a string - Version number b. + * @param {string} a Version number a. + * @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) { var i, diff; @@ -191,17 +205,20 @@ var App = function () { return segmentsA.length - segmentsB.length; } - /* start(callback) - * This methods starts 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. + /** + * Start the core app. * - * 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) { loadConfig(function (c) { config = c; + Log.setLogLevel(config.logLevel); + var modules = []; for (var m in config.modules) { @@ -232,9 +249,10 @@ var App = function () { }); }; - /* stop() - * This methods stops the core app. - * This calls each node_helper's STOP() function, if it exists. + /** + * Stops the core app. This calls each node_helper's STOP() function, if it + * exists. + * * Added to fix #1056 */ this.stop = function () { @@ -246,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 * Note: this is only used if running `server-only`. Otherwise @@ -261,7 +280,9 @@ var App = function () { 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", () => { Log.log("[SIGTERM] Received. Shutting down server..."); diff --git a/js/check_config.js b/js/check_config.js index cfd56824..2bab5220 100644 --- a/js/check_config.js +++ b/js/check_config.js @@ -12,13 +12,14 @@ const path = require("path"); const fs = require("fs"); const rootPath = path.resolve(__dirname + "/../"); -const config = require(rootPath + "/.eslintrc.json"); const Log = require(rootPath + "/js/logger.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 + * + * @returns {string} path and filename of the config file */ function getConfigFile() { // FIXME: This function should be in core. Do you want refactor me ;) ?, be good! @@ -29,6 +30,9 @@ function getConfigFile() { return configFileName; } +/** + * Checks the config file using eslint. + */ function checkConfigFile() { const configFileName = getConfigFile(); @@ -38,7 +42,7 @@ function checkConfigFile() { throw new Error("No config file present!"); } - // check permission + // Check permission try { fs.accessSync(configFileName, fs.F_OK); } catch (e) { @@ -54,16 +58,15 @@ function checkConfigFile() { if (err) { throw err; } - const messages = linter.verify(data, config); + const messages = linter.verify(data); if (messages.length === 0) { - Log.log("Your configuration file doesn't contain syntax errors :)"); - return true; + Log.info(Utils.colors.pass("Your configuration file doesn't contain syntax errors :)")); } else { + Log.error(Utils.colors.error("Your configuration file contains syntax errors :(")); // In case the there errors show messages and return 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!"); } }); } diff --git a/js/class.js b/js/class.js index f52ac405..9c46a894 100644 --- a/js/class.js +++ b/js/class.js @@ -57,7 +57,9 @@ : prop[name]; } - // The dummy class constructor + /** + * The dummy class constructor + */ function Class() { // All construction is actually done in the init method 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) { if (obj === null || typeof obj !== "object") { return obj; diff --git a/js/electron.js b/js/electron.js index 0978cd0d..216dee48 100644 --- a/js/electron.js +++ b/js/electron.js @@ -15,6 +15,9 @@ const BrowserWindow = electron.BrowserWindow; // be closed automatically when the JavaScript object is garbage collected. let mainWindow; +/** + * + */ function createWindow() { app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); var electronOptionsDefaults = { diff --git a/js/loader.js b/js/loader.js index bc376bd9..4ba75578 100644 --- a/js/loader.js +++ b/js/loader.js @@ -15,7 +15,7 @@ var Loader = (function () { /* Private Methods */ - /* loadModules() + /** * Loops thru all modules and requests load for every module. */ var loadModules = function () { @@ -43,7 +43,7 @@ var Loader = (function () { loadNextModule(); }; - /* startModules() + /** * Loops thru all modules and requests start for every module. */ var startModules = function () { @@ -56,19 +56,19 @@ var Loader = (function () { MM.modulesStarted(moduleObjects); }; - /* getAllModules() + /** * Retrieve list of all modules. * - * return array - module data as configured in config + * @returns {object[]} module data as configured in config */ var getAllModules = function () { return config.modules; }; - /* getModuleData() + /** * Generate array with module information including module paths. * - * return array - Module information. + * @returns {object[]} Module information. */ var getModuleData = function () { var modules = getAllModules(); @@ -106,11 +106,11 @@ var Loader = (function () { 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. - * argument module object - Information about the module we want to load. + * @param {object} module Information about the module we want to load. + * @param {Function} callback Function called when done. */ var loadModule = function (module, callback) { 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. * - * argument module object - Information about the module we want to load. - * argument mObj object - Modules instance. - * argument callback function - Function called when done. + * @param {object} module Information about the module we want to load. + * @param {Module} mObj Modules instance. + * @param {Function} callback Function called when done. */ var bootstrapModule = function (module, mObj, callback) { 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. * - * argument fileName string - Path of the file we want to load. - * argument callback function - Function called when done. + * @param {string} fileName Path of the file we want to load. + * @param {Function} callback Function called when done. */ var loadFile = function (fileName, callback) { var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1); @@ -215,20 +215,20 @@ var Loader = (function () { /* Public Methods */ return { - /* loadModules() + /** * Load all modules as defined in the config. */ loadModules: function () { loadModules(); }, - /* loadFile() + /** * Load a file (script or stylesheet). * Prevent double loading and search for files in the vendor folder. * - * argument fileName string - Path of the file we want to load. - * argument module Module Object - the module that calls the loadFile function. - * argument callback function - Function called when done. + * @param {string} fileName Path of the file we want to load. + * @param {Module} module The module that calls the loadFile function. + * @param {Function} callback Function called when done. */ loadFile: function (fileName, module, callback) { if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) { diff --git a/js/logger.js b/js/logger.js index 157420a7..0f33b473 100644 --- a/js/logger.js +++ b/js/logger.js @@ -19,7 +19,7 @@ root.Log = factory(root.config); } })(this, function (config) { - let logLevel = { + const logLevel = { info: Function.prototype.bind.call(console.info, console), log: Function.prototype.bind.call(console.log, console), error: Function.prototype.bind.call(console.error, console), @@ -32,13 +32,15 @@ timeStamp: Function.prototype.bind.call(console.timeStamp, console) }; - if (config && config.logLevel) { - Object.keys(logLevel).forEach(function (key, index) { - if (!config.logLevel.includes(key.toLocaleUpperCase())) { - logLevel[key] = function () {}; - } - }); - } + logLevel.setLogLevel = function (newLevel) { + if (newLevel) { + Object.keys(logLevel).forEach(function (key, index) { + if (!newLevel.includes(key.toLocaleUpperCase())) { + logLevel[key] = function () {}; + } + }); + } + }; return logLevel; }); diff --git a/js/main.js b/js/main.js index 07459f0f..a02e6496 100644 --- a/js/main.js +++ b/js/main.js @@ -11,9 +11,8 @@ var MM = (function () { /* Private Methods */ - /* createDomObjects() - * Create dom objects for all modules that - * are configured for a specific position. + /** + * Create dom objects for all modules that are configured for a specific position. */ var createDomObjects = function () { var domCreationPromises = []; @@ -42,7 +41,7 @@ var MM = (function () { dom.appendChild(moduleHeader); if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") { - moduleHeader.style = "display: none;"; + moduleHeader.style.display = "none;"; } var moduleContent = document.createElement("div"); @@ -65,10 +64,12 @@ var MM = (function () { }); }; - /* selectWrapper(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 classes = position.replace("_", " "); @@ -81,13 +82,13 @@ var MM = (function () { } }; - /* sendNotification(notification, payload, sender) + /** * Send a notification to all modules. * - * argument notification string - The identifier of the notification. - * argument payload mixed - The payload of the notification. - * argument sender Module - The module that sent the notification. - * argument sendTo Module - The module to send the notification to. (optional) + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + * @param {Module} sender The module that sent the notification. + * @param {Module} [sendTo] The (optional) module to send the notification to. */ var sendNotification = function (notification, payload, sender, sendTo) { for (var m in modules) { @@ -98,13 +99,13 @@ var MM = (function () { } }; - /* updateDom(module, speed) + /** * Update the dom for a specific module. * - * argument module Module - The module that needs an update. - * argument speed Number - The number of microseconds for the animation. (optional) + * @param {Module} module The module that needs an update. + * @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) { return new Promise(function (resolve) { @@ -126,15 +127,15 @@ var MM = (function () { }); }; - /* updateDomWithContent(module, speed, newHeader, newContent) + /** * Update the dom with the specified content * - * argument module Module - The module that needs an update. - * argument speed Number - The number of microseconds for the animation. (optional) - * argument newHeader String - The new header that is generated. - * argument newContent Domobject - The new content that is generated. + * @param {Module} module The module that needs an update. + * @param {number} [speed] The (optional) number of microseconds for the animation. + * @param {string} newHeader The new header 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) { return new Promise(function (resolve) { @@ -165,14 +166,14 @@ var MM = (function () { }); }; - /* moduleNeedsUpdate(module, newContent) + /** * Check if the content has changed. * - * argument module Module - The module to check. - * argument newHeader String - The new header that is generated. - * argument newContent Domobject - The new content that is generated. + * @param {Module} module The module to check. + * @param {string} newHeader The new header 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 moduleWrapper = document.getElementById(module.identifier); @@ -197,12 +198,12 @@ var MM = (function () { return headerNeedsUpdate || contentNeedsUpdate; }; - /* moduleNeedsUpdate(module, newContent) + /** * Update the content of a module on screen. * - * argument module Module - The module to check. - * argument newHeader String - The new header that is generated. - * argument newContent Domobject - The new content that is generated. + * @param {Module} module The module to check. + * @param {string} newHeader The new header that is generated. + * @param {HTMLElement} newContent The new content that is generated. */ var updateModuleContent = function (module, newHeader, newContent) { var moduleWrapper = document.getElementById(module.identifier); @@ -216,15 +217,20 @@ var MM = (function () { contentWrapper[0].appendChild(newContent); headerWrapper[0].innerHTML = newHeader; - headerWrapper[0].style = headerWrapper.length > 0 && newHeader ? undefined : "display: none;"; + if (headerWrapper.length > 0 && newHeader) { + delete headerWrapper[0].style; + } else { + headerWrapper[0].style.display = "none"; + } }; - /* hideModule(module, speed, callback) + /** * Hide the module. * - * argument module Module - The module to hide. - * argument speed Number - The speed of the hide animation. - * argument callback function - Called when the animation is done. + * @param {Module} module The module to hide. + * @param {number} speed The speed of the hide animation. + * @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) { options = options || {}; @@ -264,12 +270,13 @@ var MM = (function () { } }; - /* showModule(module, speed, callback) + /** * Show the module. * - * argument module Module - The module to show. - * argument speed Number - The speed of the show animation. - * argument callback function - Called when the animation is done. + * @param {Module} module The module to show. + * @param {number} speed The speed of the show animation. + * @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) { options = options || {}; @@ -323,7 +330,7 @@ var MM = (function () { } }; - /* updateWrapperStates() + /** * Checks for all positions if it has visible content. * If not, if will hide the position to prevent unwanted margins. * This method should be called by the show and hide methods. @@ -352,8 +359,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 () { // FIXME: Think about how to pass config around without breaking tests @@ -368,41 +375,41 @@ var MM = (function () { /* eslint-enable */ }; - /* setSelectionMethodsForModules() + /** * Adds special selectors on a collection of modules. * - * argument modules array - Array of modules. + * @param {Module[]} modules Array of 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) { 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) { 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) - * argument include boolean - if the filter should include or exclude the modules with the specific classes. + * @param {string|string[]} className one or multiple classnames (array or space divided). + * @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 searchClasses = className; @@ -427,12 +434,12 @@ var MM = (function () { return newModules; }; - /* exceptModule(module) + /** * 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 newModules = modules.filter(function (mod) { @@ -443,10 +450,10 @@ var MM = (function () { return newModules; }; - /* enumerate(callback) + /** * 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) { modules.map(function (module) { @@ -471,20 +478,23 @@ var MM = (function () { return { /* Public Methods */ - /* init() + /** * Main init method. */ init: function () { Log.info("Initializing MagicMirror."); loadConfig(); + + Log.setLogLevel(config.logLevel); + Translator.loadCoreTranslations(config.language); Loader.loadModules(); }, - /* modulesStarted(moduleObjects) + /** * Gets called when all modules are started. * - * argument moduleObjects array - All module instances. + * @param {Module[]} moduleObjects All module instances. */ modulesStarted: function (moduleObjects) { modules = []; @@ -499,12 +509,12 @@ var MM = (function () { createDomObjects(); }, - /* sendNotification(notification, payload, sender) + /** * Send a notification to all modules. * - * argument notification string - The identifier of the notification. - * argument payload mixed - The payload of the notification. - * argument sender Module - The module that sent the notification. + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + * @param {Module} sender The module that sent the notification. */ sendNotification: function (notification, payload, sender) { if (arguments.length < 3) { @@ -526,11 +536,11 @@ var MM = (function () { sendNotification(notification, payload, sender); }, - /* updateDom(module, speed) + /** * Update the dom for a specific module. * - * argument module Module - The module that needs an update. - * argument speed Number - The number of microseconds for the animation. (optional) + * @param {Module} module The module that needs an update. + * @param {number} [speed] The number of microseconds for the animation. */ updateDom: function (module, speed) { if (!(module instanceof Module)) { @@ -542,36 +552,36 @@ var MM = (function () { updateDom(module, speed); }, - /* getModules(module, speed) + /** * 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 () { setSelectionMethodsForModules(modules); return modules; }, - /* hideModule(module, speed, callback) + /** * Hide the module. * - * argument module Module - The module hide. - * argument speed Number - The speed of the hide animation. - * argument callback function - Called when the animation is done. - * argument options object - Optional settings for the hide method. + * @param {Module} module The module to hide. + * @param {number} speed The speed of the hide animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the hide method. */ hideModule: function (module, speed, callback, options) { module.hidden = true; hideModule(module, speed, callback, options); }, - /* showModule(module, speed, callback) + /** * Show the module. * - * argument module Module - The module show. - * argument speed Number - The speed of the show animation. - * argument callback function - Called when the animation is done. - * argument options object - Optional settings for the hide method. + * @param {Module} module The module to show. + * @param {number} speed The speed of the show animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the show method. */ showModule: function (module, speed, callback, options) { // do not change module.hidden yet, only if we really show it later diff --git a/js/module.js b/js/module.js index 4aa26a80..8bb5990e 100644 --- a/js/module.js +++ b/js/module.js @@ -2,9 +2,11 @@ /* Magic Mirror * Module Blueprint. + * @typedef {Object} Module * * By Michael Teeuw https://michaelteeuw.nl * MIT Licensed. + * */ var Module = Class.extend({ /********************************************************* @@ -29,53 +31,55 @@ var Module = Class.extend({ // Use the nunjucksEnvironment() to get it. _nunjucksEnvironment: null, - /* init() - * Is called when the module is instantiated. + /** + * Called when the module is instantiated. */ init: function () { //Log.log(this.defaults); }, - /* start() - * Is called when the module is started. + /** + * Called when the module is started. */ start: function () { Log.info("Starting module: " + this.name); }, - /* getScripts() + /** * Returns a list of scripts the module requires to be loaded. * - * return Array - An array with filenames. + * @returns {string[]} An array with filenames. */ getScripts: function () { return []; }, - /* getStyles() + /** * Returns a list of stylesheets the module requires to be loaded. * - * return Array - An array with filenames. + * @returns {string[]} An array with filenames. */ getStyles: function () { return []; }, - /* getTranslations() + /** * Returns a map of translation files the module requires to be loaded. * - * return Map - A map with langKeys and filenames. + * return Map - + * + * @returns {*} A map with langKeys and filenames. */ getTranslations: function () { 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. * 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 () { 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 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 () { 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. * 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. * - * return string - The template string of filename. + * @returns {string} The template string of filename. */ getTemplate: function () { return '
' + this.name + '
' + this.identifier + "
"; }, - /* 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. * - * return Object + * @returns {object} The data for the template */ getTemplateData: function () { return {}; }, - /* notificationReceived(notification, payload, sender) - * This method is called when a notification arrives. - * This method is called by the Magic Mirror core. + /** + * Called by the Magic Mirror core when a notification arrives. * - * argument notification string - The identifier of the notification. - * argument payload mixed - The payload of the notification. - * argument sender Module - The module that sent the notification. + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. + * @param {Module} sender The module that sent the notification. */ notificationReceived: function (notification, payload, sender) { if (sender) { @@ -154,11 +157,11 @@ var Module = Class.extend({ } }, - /** nunjucksEnvironment() + /** * Returns the nunjucks environment for the current module. * The environment is checked in the _nunjucksEnvironment instance variable. - - * @returns Nunjucks Environment + * + * @returns {object} The Nunjucks Environment */ nunjucksEnvironment: function () { if (this._nunjucksEnvironment !== null) { @@ -171,6 +174,7 @@ var Module = Class.extend({ trimBlocks: true, lstripBlocks: true }); + this._nunjucksEnvironment.addFilter("translate", function (str) { return self.translate(str); }); @@ -178,25 +182,25 @@ var Module = Class.extend({ 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. - * argument payload mixed - The payload of the notification. + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. */ socketNotificationReceived: function (notification, 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 () { Log.log(this.name + " is suspended."); }, - /* resume() - * This method is called when a module is shown. + /* + * Called when the module is shown. */ resume: function () { Log.log(this.name + " is resumed."); @@ -206,10 +210,10 @@ var Module = Class.extend({ * The methods below don"t need subclassing. * *********************************************/ - /* setData(data) + /** * Set the module data. * - * argument data object - Module data. + * @param {Module} data The module data */ setData: function (data) { this.data = data; @@ -220,18 +224,20 @@ var Module = Class.extend({ this.setConfig(data.config); }, - /* setConfig(config) + /** * 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) { 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. + * + * @returns {MMSocket} a socket object */ socket: function () { if (typeof this._socket === "undefined") { @@ -246,40 +252,39 @@ var Module = Class.extend({ return this._socket; }, - /* file(file) + /** * Retrieve the path to a module file. * - * argument file string - Filename. - * - * return string - File path. + * @param {string} file Filename + * @returns {string} the file path */ file: function (file) { return (this.data.path + "/" + file).replace("//", "/"); }, - /* loadStyles() + /** * 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) { this.loadDependencies("getStyles", callback); }, - /* loadScripts() + /** * 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) { this.loadDependencies("getScripts", callback); }, - /* loadDependencies(funcName, callback) + /** * Helper method to load all dependencies. * - * argument funcName string - Function name to call to get scripts or styles. - * argument callback function - Function called when done. + * @param {string} funcName Function name to call to get scripts or styles. + * @param {Function} callback Function called when done. */ loadDependencies: function (funcName, callback) { var self = this; @@ -300,10 +305,10 @@ var Module = Class.extend({ 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) { 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. * - * argument key string - The key of the string to translate - * argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional) - * argument defaultValue string - The default value with variables. (Optional) + * @param {string} key The key of the string to translate + * @param {string|object} [defaultValueOrVariables] The default value or variables for translating. + * @param {string} [defaultValue] The default value with variables. + * @returns {string} the translated key */ translate: function (key, defaultValueOrVariables, defaultValue) { if (typeof defaultValueOrVariables === "object") { @@ -348,41 +354,41 @@ var Module = Class.extend({ return Translator.translate(this, key) || defaultValueOrVariables || ""; }, - /* updateDom(speed) + /** * 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) { MM.updateDom(this, speed); }, - /* sendNotification(notification, payload) + /** * Send a notification to all modules. * - * argument notification string - The identifier of the notification. - * argument payload mixed - The payload of the notification. + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. */ sendNotification: function (notification, payload) { MM.sendNotification(notification, payload, this); }, - /* sendSocketNotification(notification, payload) + /** * Send a socket notification to the node helper. * - * argument notification string - The identifier of the notification. - * argument payload mixed - The payload of the notification. + * @param {string} notification The identifier of the notification. + * @param {*} payload The payload of the notification. */ sendSocketNotification: function (notification, payload) { this.socket().sendNotification(notification, payload); }, - /* hideModule(module, speed, callback) + /** * Hide this module. * - * argument speed Number - The speed of the hide animation. - * argument callback function - Called when the animation is done. - * argument options object - Optional settings for the hide method. + * @param {number} speed The speed of the hide animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the hide method. */ hide: function (speed, callback, options) { if (typeof callback === "object") { @@ -405,12 +411,12 @@ var Module = Class.extend({ ); }, - /* showModule(module, speed, callback) + /** * Show this module. * - * argument speed Number - The speed of the show animation. - * argument callback function - Called when the animation is done. - * argument options object - Optional settings for the hide method. + * @param {number} speed The speed of the show animation. + * @param {Function} callback Called when the animation is done. + * @param {object} [options] Optional settings for the show method. */ show: function (speed, callback, options) { if (typeof callback === "object") { @@ -451,11 +457,27 @@ Module.create = function (name) { 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. * - * argument a string - Version number a. - * argument a string - Version number b. + * @param {string} a Version number a. + * @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) { var i, diff; @@ -472,17 +494,3 @@ function cmpVersions(a, b) { } 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; -}; diff --git a/js/translator.js b/js/translator.js index 3a09fe0b..0f4bc00a 100644 --- a/js/translator.js +++ b/js/translator.js @@ -7,11 +7,11 @@ * MIT Licensed. */ var Translator = (function () { - /* loadJSON(file, callback) + /** * Load a JSON file via XHR. * - * argument file string - Path of the file we want to load. - * argument callback function - Function called when done. + * @param {string} file Path of the file we want to load. + * @param {Function} callback Function called when done. */ function loadJSON(file, callback) { var xhr = new XMLHttpRequest(); @@ -19,112 +19,39 @@ var Translator = (function () { xhr.open("GET", file, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { - callback(JSON.parse(stripComments(xhr.responseText))); + callback(JSON.parse(xhr.responseText)); } }; xhr.send(null); } - /* loadJSON(str, options) - * Remove any commenting from a json file so it can be parsed. - * - * argument str string - The string that contains json with comments. - * argument opts function - Strip options. - * - * return the stripped string. - */ - function stripComments(str, opts) { - // strip comments copied from: https://github.com/sindresorhus/strip-json-comments - - var singleComment = 1; - var multiComment = 2; - - function stripWithoutWhitespace() { - return ""; - } - - function stripWithWhitespace(str, start, end) { - return str.slice(start, end).replace(/\S/g, " "); - } - - opts = opts || {}; - - var currentChar; - var nextChar; - var insideString = false; - var insideComment = false; - var offset = 0; - var ret = ""; - var strip = opts.whitespace === false ? stripWithoutWhitespace : stripWithWhitespace; - - for (var i = 0; i < str.length; i++) { - currentChar = str[i]; - nextChar = str[i + 1]; - - if (!insideComment && currentChar === '"') { - var escaped = str[i - 1] === "\\" && str[i - 2] !== "\\"; - if (!escaped) { - insideString = !insideString; - } - } - - if (insideString) { - continue; - } - - if (!insideComment && currentChar + nextChar === "//") { - ret += str.slice(offset, i); - offset = i; - insideComment = singleComment; - i++; - } else if (insideComment === singleComment && currentChar + nextChar === "\r\n") { - i++; - insideComment = false; - ret += strip(str, offset, i); - offset = i; - continue; - } else if (insideComment === singleComment && currentChar === "\n") { - insideComment = false; - ret += strip(str, offset, i); - offset = i; - } else if (!insideComment && currentChar + nextChar === "/*") { - ret += str.slice(offset, i); - offset = i; - insideComment = multiComment; - i++; - continue; - } else if (insideComment === multiComment && currentChar + nextChar === "*/") { - i++; - insideComment = false; - ret += strip(str, offset, i + 1); - offset = i + 1; - continue; - } - } - - return ret + (insideComment ? strip(str.substr(offset)) : str.substr(offset)); - } - return { coreTranslations: {}, coreTranslationsFallback: {}, translations: {}, translationsFallback: {}, - /* translate(module, key, variables) + /** * Load a translation for a given key for a given module. * - * argument module Module - The module to load the translation for. - * argument key string - The key of the text to translate. - * argument variables - The variables to use within the translation template (optional) + * @param {Module} module The module to load the translation for. + * @param {string} key The key of the text to translate. + * @param {object} variables The variables to use within the translation template (optional) + * @returns {string} the translated key */ translate: function (module, key, variables) { variables = variables || {}; //Empty object by default - // Combines template and variables like: - // template: "Please wait for {timeToWait} before continuing with {work}." - // variables: {timeToWait: "2 hours", work: "painting"} - // to: "Please wait for 2 hours before continuing with painting." + /** + * Combines template and variables like: + * template: "Please wait for {timeToWait} before continuing with {work}." + * 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) { if (Object.prototype.toString.call(template) !== "[object String]") { return template; @@ -160,13 +87,13 @@ var Translator = (function () { return key; }, - /* load(module, file, isFallback, callback) + /** * Load a translation file (json) and remember the data. * - * argument module Module - The module to load the translation file for. - * argument file string - Path of the file we want to load. - * argument isFallback boolean - Flag to indicate fallback translations. - * argument callback function - Function called when done. + * @param {Module} module The module to load the translation file for. + * @param {string} file Path of the file we want to load. + * @param {boolean} isFallback Flag to indicate fallback translations. + * @param {Function} callback Function called when done. */ load: function (module, file, isFallback, callback) { if (!isFallback) { @@ -190,10 +117,10 @@ var Translator = (function () { } }, - /* loadCoreTranslations(lang) + /** * 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) { var self = this; @@ -210,7 +137,7 @@ var Translator = (function () { self.loadCoreTranslationsFallback(); }, - /* loadCoreTranslationsFallback() + /** * Load the core translations fallback. * The first language defined in translations.js will be used. */ diff --git a/js/utils.js b/js/utils.js index 74553f06..5044447d 100644 --- a/js/utils.js +++ b/js/utils.js @@ -10,7 +10,8 @@ var Utils = { colors: { warn: colors.yellow, error: colors.red, - info: colors.blue + info: colors.blue, + pass: colors.green } }; diff --git a/modules/default/alert/notificationFx.js b/modules/default/alert/notificationFx.js index 61ba59d7..89034420 100644 --- a/modules/default/alert/notificationFx.js +++ b/modules/default/alert/notificationFx.js @@ -12,7 +12,11 @@ */ (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) { for (let key in b) { @@ -24,7 +28,10 @@ } /** - * NotificationFx function + * NotificationFx constructor + * + * @param {object} options The configuration options + * @class */ function NotificationFx(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 () { // create HTML structure @@ -95,7 +101,7 @@ }; /** - * init events + * Init events */ NotificationFx.prototype._initEvents = function () { // 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 () { this.active = true; @@ -115,7 +121,7 @@ }; /** - * dismiss the notification + * Dismiss the notification */ NotificationFx.prototype.dismiss = function () { this.active = false; @@ -144,7 +150,7 @@ }; /** - * add to global namespace + * Add to global namespace */ window.NotificationFx = NotificationFx; })(window); diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js index bba7565d..af804139 100755 --- a/modules/default/calendar/calendar.js +++ b/modules/default/calendar/calendar.js @@ -205,7 +205,7 @@ Module.register("calendar", { eventWrapper.style.cssText = "color:" + this.colorForUrl(event.url); } - eventWrapper.className = "normal"; + eventWrapper.className = "normal event"; if (this.config.displaySymbol) { var symbolWrapper = document.createElement("td"); @@ -217,11 +217,7 @@ Module.register("calendar", { var symbolClass = this.symbolClassForUrl(event.url); symbolWrapper.className = "symbol align-right " + symbolClass; - var symbols = this.symbolsForUrl(event.url); - if (typeof symbols === "string") { - symbols = [symbols]; - } - + var symbols = this.symbolsForEvent(event); for (var i = 0; i < symbols.length; i++) { var symbol = document.createElement("span"); symbol.className = "fa fa-fw fa-" + symbols[i]; @@ -230,6 +226,7 @@ Module.register("calendar", { } symbolWrapper.appendChild(symbol); } + eventWrapper.appendChild(symbolWrapper); } else if (this.config.timeFormat === "dateheaders") { var blankCell = document.createElement("td"); @@ -419,7 +416,7 @@ Module.register("calendar", { * it will a localeSpecification object with the system locale time format. * * @param {number} timeFormat Specifies either 12 or 24 hour time format - * @returns {moment.LocaleSpecification} + * @returns {moment.LocaleSpecification} formatted time */ getLocaleSpecification: function (timeFormat) { switch (timeFormat) { @@ -435,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. - * - * return bool - Has calendar url + * @param {string} url The calendar url + * @returns {boolean} True if the calendar config contains the url, False otherwise */ hasCalendarURL: function (url) { for (var c in this.config.calendars) { @@ -453,10 +449,10 @@ Module.register("calendar", { return false; }, - /* createEventList() + /** * Creates the sorted list of all events. * - * return array - Array with events. + * @returns {object[]} Array with events. */ createEventList: function () { var events = []; @@ -467,6 +463,7 @@ Module.register("calendar", { var calendar = this.calendarData[c]; for (var e in calendar) { var event = JSON.parse(JSON.stringify(calendar[e])); // clone object + if (event.endDate < now) { continue; } @@ -536,10 +533,12 @@ Module.register("calendar", { return false; }, - /* createEventList(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) { this.sendSocketNotification("ADD_CALENDAR", { @@ -558,94 +557,100 @@ Module.register("calendar", { }, /** - * symbolsForUrl(url) - * Retrieves the symbols for a specific url. + * Retrieves the symbols for a specific event. * - * argument url string - Url to look for. - * - * return string/array - The Symbols + * @param {object} event Event to look for. + * @returns {string[]} The symbols */ - symbolsForUrl: function (url) { - return this.getCalendarProperty(url, "symbol", this.config.defaultSymbol); + symbolsForEvent: function (event) { + let symbols = this.getCalendarPropertyAsArray(event.url, "symbol", this.config.defaultSymbol); + + if (event.recurringEvent === true && this.hasCalendarProperty(event.url, "recurringSymbol")) { + symbols = this.mergeUnique(this.getCalendarPropertyAsArray(event.url, "recurringSymbol", this.config.defaultSymbol), symbols); + } + + if (event.fullDayEvent === true && this.hasCalendarProperty(event.url, "fullDaySymbol")) { + symbols = this.mergeUnique(this.getCalendarPropertyAsArray(event.url, "fullDaySymbol", this.config.defaultSymbol), symbols); + } + + return symbols; + }, + + mergeUnique: function (arr1, arr2) { + return arr1.concat( + arr2.filter(function (item) { + return arr1.indexOf(item) === -1; + }) + ); }, /** - * symbolClassForUrl(url) - * Retrieves the symbolClass for a specific url. + * Retrieves the symbolClass for a specific calendar url. * - * @param url string - Url to look for. - * - * @returns string + * @param {string} url The calendar url + * @returns {string} The class to be used for the symbols of the calendar */ symbolClassForUrl: function (url) { return this.getCalendarProperty(url, "symbolClass", ""); }, /** - * titleClassForUrl(url) - * Retrieves the titleClass for a specific url. + * Retrieves the titleClass for a specific calendar url. * - * @param url string - Url to look for. - * - * @returns string + * @param {string} url The calendar url + * @returns {string} The class to be used for the title of the calendar */ titleClassForUrl: function (url) { return this.getCalendarProperty(url, "titleClass", ""); }, /** - * timeClassForUrl(url) - * Retrieves the timeClass for a specific url. + * Retrieves the timeClass for a specific calendar url. * - * @param url string - Url to look for. - * - * @returns string + * @param {string} url The calendar url + * @returns {string} The class to be used for the time of the calendar */ timeClassForUrl: function (url) { 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. - * - * return string - The name of the calendar + * @param {string} url The calendar url + * @returns {string} The name of the calendar */ calendarNameForUrl: function (url) { 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. - * - * return string - The Color + * @param {string} url The calendar url + * @returns {string} The color */ colorForUrl: function (url) { 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. - * - * return string - The Symbol + * @param {string} url The calendar url + * @returns {string} The title */ countTitleForUrl: function (url) { 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. - * argument property string - Property to look for. - * argument defaultValue string - Value if property is not found. - * - * return string - The Property + * @param {string} url The calendar url + * @param {string} property The property to look for + * @param {string} defaultValue The value if the property is not found + * @returns {*} The property */ getCalendarProperty: function (url, property, defaultValue) { for (var c in this.config.calendars) { @@ -658,6 +663,16 @@ Module.register("calendar", { return defaultValue; }, + getCalendarPropertyAsArray: function (url, property, defaultValue) { + let p = this.getCalendarProperty(url, property, defaultValue); + if (!(p instanceof Array)) p = [p]; + return p; + }, + + hasCalendarProperty: function (url, property) { + return !!this.getCalendarProperty(url, property, undefined); + }, + /** * Shortens a string if it's longer than maxLength and add a ellipsis to the end * @@ -711,22 +726,27 @@ Module.register("calendar", { } }, - /* capFirst(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) { return string.charAt(0).toUpperCase() + string.slice(1); }, - /* titleTransform(title) + /** * Transforms the title of an event for usage. * Replaces parts of the text as defined in config.titleReplace. * Shortens title based on config.maxTitleLength and config.wrapEvents * - * argument title string - The title to transform. - * - * return string - The transformed title. + * @param {string} title The title to transform. + * @param {object} titleReplace Pairs of strings to be replaced in the 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) { for (var needle in titleReplace) { @@ -745,7 +765,7 @@ Module.register("calendar", { return title; }, - /* broadcastEvents() + /** * Broadcasts the events to all other modules for reuse. * The all events available in one array, sorted on startdate. */ @@ -755,7 +775,7 @@ Module.register("calendar", { var calendar = this.calendarData[url]; for (var e in calendar) { var event = cloneObject(calendar[e]); - event.symbol = this.symbolsForUrl(url); + event.symbol = this.symbolsForEvent(event); event.calendarName = this.calendarNameForUrl(url); event.color = this.colorForUrl(url); delete event.url; diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index e6e13916..0f8cfb24 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -6,10 +6,28 @@ */ const Log = require("../../../js/logger.js"); const ical = require("ical"); -const moment = require("moment"); const request = require("request"); -const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNumberOfDays, auth, includePastEvents) { +/** + * 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 self = this; let reloadTimer = null; @@ -18,7 +36,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu let fetchFailedCallback = function () {}; let eventsReceivedCallback = function () {}; - /* fetchCalendar() + /** * Initiates calendar fetch. */ const fetchCalendar = function () { @@ -264,6 +282,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu startDate: startDate.format("x"), endDate: endDate.format("x"), fullDayEvent: isFullDayEvent(event), + recurringEvent: true, class: event.class, firstYear: event.start.getFullYear(), location: location, @@ -327,14 +346,14 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu return a.startDate - b.startDate; }); - events = newEvents; + events = newEvents.slice(0, maximumEntries); self.broadcastEvents(); scheduleTimer(); }); }; - /* scheduleTimer() + /** * Schedule the timer for the next update. */ const scheduleTimer = function () { @@ -344,12 +363,11 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu }, reloadInterval); }; - /* isFullDayEvent(event) + /** * Checks if an event is a fullday event. * - * argument event object - The event object to check. - * - * return bool - The event is a fullday event. + * @param {object} event The event object to check. + * @returns {boolean} True if the event is a fullday event, false otherwise */ const isFullDayEvent = function (event) { if (event.start.length === 8 || event.start.dateOnly) { @@ -367,14 +385,13 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu return false; }; - /* timeFilterApplies() + /** * Determines if the user defined time filter should apply * - * argument now Date - Date object using previously created object for consistency - * argument endDate Moment - 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 - * - * return bool - The event should be filtered out + * @param {Date} now Date object using previously created object for consistency + * @param {Moment} endDate Moment object representing the event end date + * @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 */ const timeFilterApplies = function (now, endDate, filter) { if (filter) { @@ -389,12 +406,11 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu return false; }; - /* getTitleFromEvent(event) + /** * Gets the title from the event. * - * argument event object - The event object to check. - * - * return string - The title of the event, or "Event" if no title is found. + * @param {object} event The event object to check. + * @returns {string} The title of the event, or "Event" if no title is found. */ const getTitleFromEvent = function (event) { let title = "Event"; @@ -425,14 +441,14 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu /* public methods */ - /* startFetch() + /** * Initiate fetchCalendar(); */ this.startFetch = function () { fetchCalendar(); }; - /* broadcastItems() + /** * Broadcast the existing events. */ this.broadcastEvents = function () { @@ -440,37 +456,37 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu eventsReceivedCallback(self); }; - /* onReceive(callback) + /** * Sets the on success callback * - * argument callback function - The on success callback. + * @param {Function} callback The on success callback. */ this.onReceive = function (callback) { eventsReceivedCallback = callback; }; - /* onError(callback) + /** * Sets the on error callback * - * argument callback function - The on error callback. + * @param {Function} callback The on error callback. */ this.onError = function (callback) { fetchFailedCallback = callback; }; - /* url() + /** * Returns the url of this fetcher. * - * return string - The url of this fetcher. + * @returns {string} The url of this fetcher. */ this.url = function () { return url; }; - /* events() + /** * 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 () { return events; diff --git a/modules/default/calendar/node_helper.js b/modules/default/calendar/node_helper.js index 5248919b..862755a0 100644 --- a/modules/default/calendar/node_helper.js +++ b/modules/default/calendar/node_helper.js @@ -4,7 +4,6 @@ * By Michael Teeuw https://michaelteeuw.nl * MIT Licensed. */ - const NodeHelper = require("node_helper"); const validUrl = require("valid-url"); const CalendarFetcher = require("./calendarfetcher.js"); @@ -20,18 +19,24 @@ module.exports = NodeHelper.create({ // Override socketNotificationReceived method. socketNotificationReceived: function (notification, payload) { if (notification === "ADD_CALENDAR") { - this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.id); + this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.id); } }, - /* createFetcher(url, reloadInterval) + /** * Creates a fetcher for a new url if it doesn't exist yet. * Otherwise it reuses the existing one. * - * attribute url string - URL of the news feed. - * attribute reloadInterval number - Reload interval in milliseconds. + * @param {string} url The url of the calendar + * @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, maximumNumberOfDays, auth, broadcastPastEvents, identifier) { + createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, identifier) { var self = this; if (!validUrl.isUri(url)) { @@ -42,7 +47,7 @@ module.exports = NodeHelper.create({ var fetcher; if (typeof self.fetchers[identifier + url] === "undefined") { Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval); - fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumNumberOfDays, auth, broadcastPastEvents); + fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents); fetcher.onReceive(function (fetcher) { self.sendSocketNotification("CALENDAR_EVENTS", { diff --git a/modules/default/clock/clock.js b/modules/default/clock/clock.js index cfcd1e40..1c7109b6 100644 --- a/modules/default/clock/clock.js +++ b/modules/default/clock/clock.js @@ -152,6 +152,13 @@ Module.register("clock", { 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) { var formatString = hourSymbol + ":mm"; if (config.showPeriod && config.timeFormat !== 24) { @@ -159,6 +166,7 @@ Module.register("clock", { } return moment(time).format(formatString); } + if (this.config.showSunTimes) { const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon); const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset); diff --git a/modules/default/clock/faces/face-001.svg b/modules/default/clock/faces/face-001.svg index 13436c76..abd08ce7 100644 --- a/modules/default/clock/faces/face-001.svg +++ b/modules/default/clock/faces/face-001.svg @@ -1 +1 @@ -face-001 +face-001 diff --git a/modules/default/clock/faces/face-002.svg b/modules/default/clock/faces/face-002.svg index a3ee3682..1ec31049 100644 --- a/modules/default/clock/faces/face-002.svg +++ b/modules/default/clock/faces/face-002.svg @@ -1 +1 @@ -face-002 +face-002 diff --git a/modules/default/clock/faces/face-003.svg b/modules/default/clock/faces/face-003.svg index d3fc32de..7cfeebac 100644 --- a/modules/default/clock/faces/face-003.svg +++ b/modules/default/clock/faces/face-003.svg @@ -1 +1 @@ -face-003 +face-003 diff --git a/modules/default/clock/faces/face-004.svg b/modules/default/clock/faces/face-004.svg index 140955d7..bc97588c 100644 --- a/modules/default/clock/faces/face-004.svg +++ b/modules/default/clock/faces/face-004.svg @@ -1 +1 @@ -face-004 +face-004 diff --git a/modules/default/clock/faces/face-005.svg b/modules/default/clock/faces/face-005.svg index fb52a435..0bc1b430 100644 --- a/modules/default/clock/faces/face-005.svg +++ b/modules/default/clock/faces/face-005.svg @@ -1 +1 @@ -faces +faces diff --git a/modules/default/clock/faces/face-006.svg b/modules/default/clock/faces/face-006.svg index a9eff826..63d1c935 100644 --- a/modules/default/clock/faces/face-006.svg +++ b/modules/default/clock/faces/face-006.svg @@ -1 +1 @@ -face-006 +face-006 diff --git a/modules/default/clock/faces/face-007.svg b/modules/default/clock/faces/face-007.svg index b0dde834..e557f55f 100644 --- a/modules/default/clock/faces/face-007.svg +++ b/modules/default/clock/faces/face-007.svg @@ -1 +1 @@ -face-007 +face-007 diff --git a/modules/default/clock/faces/face-008.svg b/modules/default/clock/faces/face-008.svg index 551ea7df..6fadb396 100644 --- a/modules/default/clock/faces/face-008.svg +++ b/modules/default/clock/faces/face-008.svg @@ -1 +1 @@ -face-008 +face-008 diff --git a/modules/default/clock/faces/face-009.svg b/modules/default/clock/faces/face-009.svg index cd3720f2..bd207e09 100644 --- a/modules/default/clock/faces/face-009.svg +++ b/modules/default/clock/faces/face-009.svg @@ -1 +1 @@ -face-009 +face-009 diff --git a/modules/default/clock/faces/face-010.svg b/modules/default/clock/faces/face-010.svg index d0af3acb..8c5e5848 100644 --- a/modules/default/clock/faces/face-010.svg +++ b/modules/default/clock/faces/face-010.svg @@ -1 +1 @@ -face-010 +face-010 diff --git a/modules/default/clock/faces/face-011.svg b/modules/default/clock/faces/face-011.svg index 2d5f27ba..9886fed5 100644 --- a/modules/default/clock/faces/face-011.svg +++ b/modules/default/clock/faces/face-011.svg @@ -1 +1 @@ -face-011 +face-011 diff --git a/modules/default/clock/faces/face-012.svg b/modules/default/clock/faces/face-012.svg index 8096d492..cfd80698 100644 --- a/modules/default/clock/faces/face-012.svg +++ b/modules/default/clock/faces/face-012.svg @@ -1 +1 @@ -face-012 +face-012 diff --git a/modules/default/newsfeed/newsfeed.js b/modules/default/newsfeed/newsfeed.js index 70809522..2ff02888 100644 --- a/modules/default/newsfeed/newsfeed.js +++ b/modules/default/newsfeed/newsfeed.js @@ -84,11 +84,11 @@ Module.register("newsfeed", { // Override dom generator. getDom: function () { - var wrapper = document.createElement("div"); + const wrapper = document.createElement("div"); if (this.config.feedUrl) { wrapper.className = "small bright"; - wrapper.innerHTML = this.translate("configuration_changed"); + wrapper.innerHTML = this.translate("MODULE_CONFIG_CHANGED", { MODULE_NAME: "Newsfeed" }); return wrapper; } @@ -99,7 +99,7 @@ Module.register("newsfeed", { if (this.newsItems.length > 0) { // this.config.showFullArticle is a run-time configuration, triggered by optional notifications if (!this.config.showFullArticle && (this.config.showSourceTitle || this.config.showPublishDate)) { - var sourceAndTimestamp = document.createElement("div"); + const sourceAndTimestamp = document.createElement("div"); sourceAndTimestamp.className = "newsfeed-source light small dimmed"; if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") { @@ -157,22 +157,22 @@ Module.register("newsfeed", { } if (!this.config.showFullArticle) { - var title = document.createElement("div"); + const title = document.createElement("div"); title.className = "newsfeed-title bright medium light" + (!this.config.wrapTitle ? " no-wrap" : ""); title.innerHTML = this.newsItems[this.activeItem].title; wrapper.appendChild(title); } if (this.isShowingDescription) { - var description = document.createElement("div"); + const description = document.createElement("div"); description.className = "newsfeed-desc small light" + (!this.config.wrapDescription ? " no-wrap" : ""); - var txtDesc = this.newsItems[this.activeItem].description; + const txtDesc = this.newsItems[this.activeItem].description; description.innerHTML = this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc; wrapper.appendChild(description); } if (this.config.showFullArticle) { - var fullArticle = document.createElement("iframe"); + const fullArticle = document.createElement("iframe"); fullArticle.className = ""; fullArticle.style.width = "100vw"; // very large height value to allow scrolling @@ -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; }, - /* registerFeeds() - * registers the feeds to be used by the backend. + /** + * Registers the feeds to be used by the backend. */ registerFeeds: function () { 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. * - * 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) { var newsItems = []; @@ -274,12 +274,11 @@ Module.register("newsfeed", { this.newsItems = newsItems; }, - /* subscribedToFeed(feedUrl) + /** * Check if this module is configured to show this feed. * - * attribute feedUrl string - Url of the feed to check. - * - * returns bool + * @param {string} feedUrl Url of the feed to check. + * @returns {boolean} True if it is subscribed, false otherwise */ subscribedToFeed: function (feedUrl) { for (var f in this.config.feeds) { @@ -291,12 +290,11 @@ Module.register("newsfeed", { 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. - * - * returns string + * @param {string} feedUrl Url of the feed + * @returns {string} The title of the feed */ titleForFeed: function (feedUrl) { for (var f in this.config.feeds) { @@ -308,7 +306,7 @@ Module.register("newsfeed", { return ""; }, - /* scheduleUpdateInterval() + /** * Schedule visual update. */ scheduleUpdateInterval: function () { @@ -332,17 +330,6 @@ Module.register("newsfeed", { }, this.config.updateInterval); }, - /* capitalizeFirstLetter(string) - * Capitalizes the first character of a string. - * - * argument string string - Input string. - * - * return string - Capitalized output string. - */ - capitalizeFirstLetter: function (string) { - return string.charAt(0).toUpperCase() + string.slice(1); - }, - resetDescrOrFullArticleAndTimer: function () { this.isShowingDescription = this.config.showDescription; this.config.showFullArticle = false; @@ -356,7 +343,7 @@ Module.register("newsfeed", { }, notificationReceived: function (notification, payload, sender) { - var before = this.activeItem; + const before = this.activeItem; if (notification === "ARTICLE_NEXT") { this.activeItem++; if (this.activeItem >= this.newsItems.length) { diff --git a/modules/default/newsfeed/fetcher.js b/modules/default/newsfeed/newsfeedfetcher.js similarity index 62% rename from modules/default/newsfeed/fetcher.js rename to modules/default/newsfeed/newsfeedfetcher.js index da9616dc..20e45258 100644 --- a/modules/default/newsfeed/fetcher.js +++ b/modules/default/newsfeed/newsfeedfetcher.js @@ -1,55 +1,56 @@ /* Magic Mirror - * Fetcher + * Node Helper: Newsfeed - NewsfeedFetcher * * By Michael Teeuw https://michaelteeuw.nl * MIT Licensed. */ - const Log = require("../../../js/logger.js"); const FeedMe = require("feedme"); const request = require("request"); const iconv = require("iconv-lite"); -/* Fetcher +/** * Responsible for requesting an update on the set interval and broadcasting the data. * - * attribute url string - URL of the news feed. - * attribute reloadInterval number - Reload interval in milliseconds. - * attribute logFeedWarnings boolean - Log warnings when there is an error parsing a news article. + * @param {string} url URL of the news feed. + * @param {number} reloadInterval Reload interval in milliseconds. + * @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 self = this; + + let reloadTimer = null; + let items = []; + + let fetchFailedCallback = function () {}; + let itemsReceivedCallback = function () {}; -var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) { - var self = this; if (reloadInterval < 1000) { reloadInterval = 1000; } - var reloadTimer = null; - var items = []; - - var fetchFailedCallback = function () {}; - var itemsReceivedCallback = function () {}; - /* private methods */ - /* fetchNews() + /** * Request the new items. */ - var fetchNews = function () { + const fetchNews = function () { clearTimeout(reloadTimer); reloadTimer = null; items = []; - var parser = new FeedMe(); + const parser = new FeedMe(); parser.on("item", function (item) { - var title = item.title; - var description = item.description || item.summary || item.content || ""; - var pubdate = item.pubdate || item.published || item.updated || item["dc:date"]; - var url = item.url || item.link || ""; + const title = item.title; + let description = item.description || item.summary || item.content || ""; + const pubdate = item.pubdate || item.published || item.updated || item["dc:date"]; + const url = item.url || item.link || ""; if (title && pubdate) { - var regex = /(<([^>]+)>)/gi; + const regex = /(<([^>]+)>)/gi; description = description.toString().replace(regex, ""); items.push({ @@ -77,10 +78,17 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) { scheduleTimer(); }); - var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); - var headers = { "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)", "Cache-Control": "max-age=0, no-cache, no-store, must-revalidate", Pragma: "no-cache" }; + const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); + const opts = { + headers: { + "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)", + "Cache-Control": "max-age=0, no-cache, no-store, must-revalidate", + Pragma: "no-cache" + }, + encoding: null + }; - request({ uri: url, encoding: null, headers: headers }) + request(url, opts) .on("error", function (error) { fetchFailedCallback(self, error); scheduleTimer(); @@ -89,10 +97,10 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) { .pipe(parser); }; - /* scheduleTimer() + /** * Schedule the timer for the next update. */ - var scheduleTimer = function () { + const scheduleTimer = function () { clearTimeout(reloadTimer); reloadTimer = setTimeout(function () { fetchNews(); @@ -101,10 +109,10 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) { /* public methods */ - /* setReloadInterval() + /** * 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) { if (interval > 1000 && interval < reloadInterval) { @@ -112,14 +120,14 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) { } }; - /* startFetch() + /** * Initiate fetchNews(); */ this.startFetch = function () { fetchNews(); }; - /* broadcastItems() + /** * Broadcast the existing items. */ this.broadcastItems = function () { @@ -148,4 +156,4 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) { }; }; -module.exports = Fetcher; +module.exports = NewsfeedFetcher; diff --git a/modules/default/newsfeed/node_helper.js b/modules/default/newsfeed/node_helper.js index 4ccef958..bdfcbfcc 100644 --- a/modules/default/newsfeed/node_helper.js +++ b/modules/default/newsfeed/node_helper.js @@ -7,7 +7,7 @@ const NodeHelper = require("node_helper"); const validUrl = require("valid-url"); -const Fetcher = require("./fetcher.js"); +const NewsfeedFetcher = require("./newsfeedfetcher.js"); const Log = require("../../../js/logger"); module.exports = NodeHelper.create({ @@ -24,45 +24,43 @@ module.exports = NodeHelper.create({ } }, - /* createFetcher(feed, config) + /** * Creates a fetcher for a new feed if it doesn't exist yet. * Otherwise it reuses the existing one. * - * attribute feed object - A feed object. - * attribute config object - A configuration object containing reload interval in milliseconds. + * @param {object} feed The feed object. + * @param {object} config The configuration object. */ createFetcher: function (feed, config) { - var self = this; - - var url = feed.url || ""; - var encoding = feed.encoding || "UTF-8"; - var reloadInterval = feed.reloadInterval || config.reloadInterval || 5 * 60 * 1000; + const url = feed.url || ""; + const encoding = feed.encoding || "UTF-8"; + const reloadInterval = feed.reloadInterval || config.reloadInterval || 5 * 60 * 1000; if (!validUrl.isUri(url)) { - self.sendSocketNotification("INCORRECT_URL", url); + this.sendSocketNotification("INCORRECT_URL", url); return; } - var fetcher; - if (typeof self.fetchers[url] === "undefined") { + let fetcher; + if (typeof this.fetchers[url] === "undefined") { Log.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval); - fetcher = new Fetcher(url, reloadInterval, encoding, config.logFeedWarnings); + fetcher = new NewsfeedFetcher(url, reloadInterval, encoding, config.logFeedWarnings); - fetcher.onReceive(function (fetcher) { - self.broadcastFeeds(); + fetcher.onReceive(() => { + this.broadcastFeeds(); }); - fetcher.onError(function (fetcher, error) { - self.sendSocketNotification("FETCH_ERROR", { + fetcher.onError((fetcher, error) => { + this.sendSocketNotification("FETCH_ERROR", { url: fetcher.url(), error: error }); }); - self.fetchers[url] = fetcher; + this.fetchers[url] = fetcher; } else { Log.log("Use existing news fetcher for url: " + url); - fetcher = self.fetchers[url]; + fetcher = this.fetchers[url]; fetcher.setReloadInterval(reloadInterval); fetcher.broadcastItems(); } @@ -70,7 +68,7 @@ module.exports = NodeHelper.create({ fetcher.startFetch(); }, - /* broadcastFeeds() + /** * Creates an object with all feed items of the different registered feeds, * and broadcasts these using sendSocketNotification. */ diff --git a/modules/default/newsfeed/translations/de.json b/modules/default/newsfeed/translations/de.json deleted file mode 100644 index a11eb323..00000000 --- a/modules/default/newsfeed/translations/de.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "configuration_changed": "Die Konfigurationsoptionen für das Newsfeed-Modul haben sich geändert. \nBitte überprüfen Sie die Dokumentation." -} diff --git a/modules/default/newsfeed/translations/en.json b/modules/default/newsfeed/translations/en.json deleted file mode 100644 index 9e804445..00000000 --- a/modules/default/newsfeed/translations/en.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "configuration_changed": "The configuration options for the newsfeed module have changed.\nPlease check the documentation." -} diff --git a/modules/default/newsfeed/translations/es.json b/modules/default/newsfeed/translations/es.json deleted file mode 100644 index b1124b6e..00000000 --- a/modules/default/newsfeed/translations/es.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "configuration_changed": "Las opciones de configuración para el módulo de suministro de noticias han cambiado. \nVerifique la documentación." -} diff --git a/modules/default/newsfeed/translations/fr.json b/modules/default/newsfeed/translations/fr.json deleted file mode 100644 index fa6d522e..00000000 --- a/modules/default/newsfeed/translations/fr.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "configuration_changed": "Les options de configuration du module newsfeed ont changé. \nVeuillez consulter la documentation." -} diff --git a/modules/default/updatenotification/node_helper.js b/modules/default/updatenotification/node_helper.js index 0c206c5f..973dca51 100644 --- a/modules/default/updatenotification/node_helper.js +++ b/modules/default/updatenotification/node_helper.js @@ -72,7 +72,7 @@ module.exports = NodeHelper.create({ performFetch: function () { var self = this; simpleGits.forEach((sg) => { - sg.git.fetch().status((err, data) => { + sg.git.fetch(["--dry-run"]).status((err, data) => { data.module = sg.module; if (!err) { sg.git.log({ "-1": null }, (err, data2) => { diff --git a/modules/default/weather/providers/weathergov.js b/modules/default/weather/providers/weathergov.js index c24e4de1..1b28bba1 100755 --- a/modules/default/weather/providers/weathergov.js +++ b/modules/default/weather/providers/weathergov.js @@ -3,76 +3,155 @@ /* Magic Mirror * Module: Weather * Provider: weather.gov + * https://weather-gov.github.io/api/general-faqs * - * By Vince Peri + * Original by Vince Peri * MIT Licensed. * * This class is a provider for weather.gov. * Note that this is only for US locations (lat and lon) and does not require an API key - * Since it is free, there are some items missing - like sunrise, sunset, humidity, etc. + * Since it is free, there are some items missing - like sunrise, sunset */ + WeatherProvider.register("weathergov", { // Set the name of the provider. // This isn't strictly necessary, since it will fallback to the provider identifier // But for debugging (and future alerts) it would be nice to have the real name. providerName: "Weather.gov", + // Flag all needed URLs availability + configURLs: false, + + //This API has multiple urls involved + forecastURL: "tbd", + forecastHourlyURL: "tbd", + forecastGridDataURL: "tbd", + observationStationsURL: "tbd", + stationObsURL: "tbd", + + // Called to set the config, this config is the same as the weather module's config. + setConfig: function (config) { + this.config = config; + (this.config.apiBase = "https://api.weather.gov"), this.fetchWxGovURLs(this.config); + }, + + // Called when the weather provider is about to start. + start: function () { + Log.info(`Weather provider: ${this.providerName} started.`); + }, + + // This returns the name of the fetched location or an empty string. + fetchedLocation: function () { + return this.fetchedLocationName || ""; + }, + // Overwrite the fetchCurrentWeather method. fetchCurrentWeather() { - this.fetchData(this.getUrl()) + if (!this.configURLs) { + Log.info("fetch wx waiting on config URLs"); + return; + } + this.fetchData(this.stationObsURL) .then((data) => { - if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) { + if (!data || !data.properties) { // Did not receive usable new data. - // Maybe this needs a better check? return; } - - const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties.periods[0]); + const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties); this.setCurrentWeather(currentWeather); }) .catch(function (request) { - Log.error("Could not load data ... ", request); + Log.error("Could not load station obs data ... ", request); }) .finally(() => this.updateAvailable()); }, - // Overwrite the fetchCurrentWeather method. + // Overwrite the fetchWeatherForecast method. fetchWeatherForecast() { - this.fetchData(this.getUrl()) + if (!this.configURLs) { + Log.info("fetch wx waiting on config URLs"); + return; + } + this.fetchData(this.forecastURL) .then((data) => { if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) { // Did not receive usable new data. - // Maybe this needs a better check? return; } - const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods); this.setWeatherForecast(forecast); }) .catch(function (request) { - Log.error("Could not load data ... ", request); + Log.error("Could not load forecast hourly data ... ", request); }) .finally(() => this.updateAvailable()); }, /** Weather.gov Specific Methods - These are not part of the default provider methods */ + /* - * Gets the complete url for the request + * Get specific URLs */ - getUrl() { - return this.config.apiBase + this.config.lat + "," + this.config.lon + this.config.weatherEndpoint; + fetchWxGovURLs(config) { + this.fetchData(`${config.apiBase}/points/${config.lat},${config.lon}`) + .then((data) => { + if (!data || !data.properties) { + // points URL did not respond with usable data. + return; + } + this.fetchedLocationName = data.properties.relativeLocation.properties.city + ", " + data.properties.relativeLocation.properties.state; + Log.log("Forecast location is " + this.fetchedLocationName); + this.forecastURL = data.properties.forecast; + this.forecastHourlyURL = data.properties.forecastHourly; + this.forecastGridDataURL = data.properties.forecastGridData; + this.observationStationsURL = data.properties.observationStations; + // with this URL, we chain another promise for the station obs URL + return this.fetchData(data.properties.observationStations); + }) + .then((obsData) => { + if (!obsData || !obsData.features) { + // obs station URL did not respond with usable data. + return; + } + this.stationObsURL = obsData.features[0].id + "/observations/latest"; + }) + .catch((err) => { + Log.error(err); + }) + .finally(() => { + // excellent, let's fetch some actual wx data + this.configURLs = true; + this.fetchCurrentWeather(); + }); }, /* * Generate a WeatherObject based on currentWeatherInformation + * Weather.gov API uses specific units; API does not include choice of units + * ... object needs data in units based on config! */ generateWeatherObjectFromCurrentWeather(currentWeatherData) { const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); - currentWeather.temperature = currentWeatherData.temperature; - currentWeather.windSpeed = currentWeatherData.windSpeed.split(" ", 1); - currentWeather.windDirection = this.convertWindDirection(currentWeatherData.windDirection); - currentWeather.weatherType = this.convertWeatherType(currentWeatherData.shortForecast, currentWeatherData.isDaytime); + currentWeather.date = moment(currentWeatherData.timestamp); + currentWeather.temperature = this.convertTemp(currentWeatherData.temperature.value); + currentWeather.windSpeed = this.covertSpeed(currentWeatherData.windSpeed.value); + currentWeather.windDirection = currentWeatherData.windDirection.value; + currentWeather.minTemperature = this.convertTemp(currentWeatherData.minTemperatureLast24Hours.value); + currentWeather.maxTemperature = this.convertTemp(currentWeatherData.maxTemperatureLast24Hours.value); + currentWeather.humidity = Math.round(currentWeatherData.relativeHumidity.value); + currentWeather.rain = null; + currentWeather.snow = null; + currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value); + currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.heatIndex.value); + + let isDaytime = true; + if (currentWeatherData.icon.includes("day")) { + isDaytime = true; + } else { + isDaytime = false; + } + currentWeather.weatherType = this.convertWeatherType(currentWeatherData.textDescription, isDaytime); // determine the sunrise/sunset times - not supplied in weather.gov data let times = this.calcAstroData(this.config.lat, this.config.lon); @@ -124,7 +203,7 @@ WeatherProvider.register("weathergov", { // specify date weather.date = moment(forecast.startTime); - // If the first value of today is later than 17:00, we have an icon at least! + // use the forecast isDayTime attribute to help build the weatherType label weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime); } @@ -148,6 +227,34 @@ WeatherProvider.register("weathergov", { return days.slice(1); }, + /* + * Unit conversions + */ + // conversion to fahrenheit + convertTemp(temp) { + if (this.config.tempUnits === "imperial") { + return (9 / 5) * temp + 32; + } else { + return temp; + } + }, + // conversion to mph + covertSpeed(metSec) { + if (this.config.windUnits === "imperial") { + return metSec * 2.23694; + } else { + return metSec; + } + }, + // conversion to inches + convertLength(meters) { + if (this.config.units === "imperial") { + return meters * 39.3701; + } else { + return meters; + } + }, + /* * Calculate the astronomical data */ diff --git a/modules/default/weather/weather.js b/modules/default/weather/weather.js index 150ba084..73c493e0 100644 --- a/modules/default/weather/weather.js +++ b/modules/default/weather/weather.js @@ -15,7 +15,6 @@ Module.register("weather", { location: false, locationID: false, - appid: "", units: config.units, tempUnits: config.units, @@ -43,8 +42,10 @@ Module.register("weather", { initialLoadDelay: 0, // 0 seconds delay retryDelay: 2500, + apiKey: "", + apiSecret: "", apiVersion: "2.5", - apiBase: "https://api.openweathermap.org/data/", + apiBase: "https://api.openweathermap.org/data/", // TODO: this should not be part of the weather.js file, but should be contained in the openweatherprovider weatherEndpoint: "/weather", appendLocationNameToHeader: true, diff --git a/modules/default/weather/weatherprovider.js b/modules/default/weather/weatherprovider.js index cfcb3ef6..ed22054b 100644 --- a/modules/default/weather/weatherprovider.js +++ b/modules/default/weather/weatherprovider.js @@ -12,7 +12,7 @@ var WeatherProvider = Class.extend({ // Weather Provider Properties providerName: null, - // The following properties have accestor methods. + // The following properties have accessor methods. // Try to not access them directly. currentWeatherObject: null, weatherForecastArray: null, @@ -119,6 +119,9 @@ WeatherProvider.providers = []; /** * 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.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails); @@ -126,6 +129,10 @@ WeatherProvider.register = function (providerIdentifier, providerDetails) { /** * 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) { providerIdentifier = providerIdentifier.toLowerCase(); diff --git a/modules/default/weatherforecast/weatherforecast.js b/modules/default/weatherforecast/weatherforecast.js index 83389911..3ff239ef 100644 --- a/modules/default/weatherforecast/weatherforecast.js +++ b/modules/default/weatherforecast/weatherforecast.js @@ -9,6 +9,8 @@ Module.register("weatherforecast", { defaults: { location: false, locationID: false, + lat: false, + lon: false, appid: "", units: config.units, maxNumberOfDays: 7, @@ -29,6 +31,7 @@ Module.register("weatherforecast", { apiVersion: "2.5", apiBase: "https://api.openweathermap.org/data/", forecastEndpoint: "forecast/daily", + excludes: false, appendLocationNameToHeader: true, calendarClass: "calendar", @@ -283,6 +286,8 @@ Module.register("weatherforecast", { var params = "?"; if (this.config.locationID) { params += "id=" + this.config.locationID; + } else if (this.config.lat && this.config.lon) { + params += "lat=" + this.config.lat + "&lon=" + this.config.lon; } else if (this.config.location) { params += "q=" + this.config.location; } else if (this.firstEvent && this.firstEvent.geo) { @@ -294,8 +299,17 @@ Module.register("weatherforecast", { return; } - params += "&cnt=" + (this.config.maxNumberOfDays < 1 || this.config.maxNumberOfDays > 17 ? 7 : this.config.maxNumberOfDays); + let numberOfDays; + if (this.config.forecastEndpoint === "forecast") { + numberOfDays = this.config.maxNumberOfDays < 1 || this.config.maxNumberOfDays > 5 ? 5 : this.config.maxNumberOfDays; + // don't get forecasts for the next day, as it would not represent the whole day + numberOfDays = numberOfDays * 8 - (Math.round(new Date().getHours() / 3) % 8); + } else { + numberOfDays = this.config.maxNumberOfDays < 1 || this.config.maxNumberOfDays > 17 ? 7 : this.config.maxNumberOfDays; + } + params += "&cnt=" + numberOfDays; + params += "&exclude=" + this.config.excludes; params += "&units=" + this.config.units; params += "&lang=" + this.config.lang; params += "&APPID=" + this.config.appid; @@ -323,15 +337,34 @@ Module.register("weatherforecast", { * argument data object - Weather information received form openweather.org. */ processWeather: function (data) { - this.fetchedLocationName = data.city.name + ", " + data.city.country; + // Forcast16 (paid) API endpoint provides this data. Onecall endpoint + // does not. + if (data.city) { + this.fetchedLocationName = data.city.name + ", " + data.city.country; + } else if (this.config.location) { + this.fetchedLocationName = this.config.location; + } else { + this.fetchedLocationName = "Unknown"; + } this.forecast = []; var lastDay = null; var forecastData = {}; - for (var i = 0, count = data.list.length; i < count; i++) { - var forecast = data.list[i]; - this.parserDataWeather(forecast); // hack issue #1017 + // Handle different structs between forecast16 and onecall endpoints + var forecastList = null; + if (data.list) { + forecastList = data.list; + } else if (data.daily) { + forecastList = data.daily; + } else { + Log.error("Unexpected forecast data"); + return undefined; + } + + for (var i = 0, count = forecastList.length; i < count; i++) { + var forecast = forecastList[i]; + forecast = this.parserDataWeather(forecast); // hack issue #1017 var day; var hour; @@ -349,7 +382,7 @@ Module.register("weatherforecast", { icon: this.config.iconTable[forecast.weather[0].icon], maxTemp: this.roundValue(forecast.temp.max), minTemp: this.roundValue(forecast.temp.min), - rain: this.processRain(forecast, data.list) + rain: this.processRain(forecast, forecastList) }; this.forecast.push(forecastData); diff --git a/package-lock.json b/package-lock.json index 15a6644a..a3214e7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "magicmirror", - "version": "2.12.0", + "version": "2.13.0-develop", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -302,6 +302,24 @@ } } }, + "@babel/runtime-corejs3": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz", + "integrity": "sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } + } + }, "@babel/template": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", @@ -458,6 +476,88 @@ "to-fast-properties": "^2.0.0" } }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -636,6 +736,24 @@ "es6-promisify": "^5.0.0" } }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } + } + }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -650,7 +768,8 @@ "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==" + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true }, "ansi-regex": { "version": "2.1.1", @@ -687,6 +806,21 @@ "integrity": "sha1-7klza2ObTxCLbp5ibG2pkwa0FpI=", "dev": true }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -983,9 +1117,9 @@ } }, "bowser": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.8.1.tgz", - "integrity": "sha512-FxxltGKqMHkVa3KtpA+kdnxH0caHPDewccyrK3vW1bsMw6Zco4vRPmMunowX0pXlDZqhxkKSpToADQI2Sk4OeQ==" + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", + "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" }, "brace-expansion": { "version": "1.1.11", @@ -1088,6 +1222,18 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, "callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", @@ -1226,6 +1372,12 @@ "resolved": "https://registry.npmjs.org/clarinet/-/clarinet-0.12.4.tgz", "integrity": "sha512-Rx9Zw8KQkoPO3/O2yPRchCZm3cGubCQiRLmmFAlbkDKobUIPP3JYul+bKILR9DIv1gSVwPQSgF8JGGkXzX8Q0w==" }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -1328,6 +1480,18 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "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": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "compare-versions": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", @@ -1467,6 +1631,12 @@ "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", "dev": true }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1758,6 +1928,23 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + } + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -1819,11 +2006,6 @@ } } }, - "dns-prefetch-control": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz", - "integrity": "sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q==" - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2078,11 +2260,18 @@ } }, "enquirer": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", - "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "requires": { - "ansi-colors": "^3.2.1" + "ansi-colors": "^4.1.1" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + } } }, "entities": { @@ -2142,6 +2331,12 @@ "is-symbol": "^1.0.2" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -2189,9 +2384,9 @@ } }, "eslint": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.3.0.tgz", - "integrity": "sha512-dJMVXwfU5PT1cj2Nv2VPPrKahKTGdX+5Dh0Q3YuKt+Y2UhdL2YbzsVaBMyG9HC0tBismlv/r1+eZqs6SMIV38Q==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.5.0.tgz", + "integrity": "sha512-vlUP10xse9sWt9SGRtcr1LAC67BENcQMFeV+w5EvLEoFe3xJ8cF1Skd0msziRx/VMC+72B4DxreCE+OR12OA6Q==", "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", @@ -2201,9 +2396,9 @@ "doctrine": "^3.0.0", "enquirer": "^2.3.5", "eslint-scope": "^5.1.0", - "eslint-utils": "^2.0.0", - "eslint-visitor-keys": "^1.2.0", - "espree": "^7.1.0", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^1.3.0", + "espree": "^7.2.0", "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", @@ -2217,7 +2412,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.14", + "lodash": "^4.17.19", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -2349,9 +2544,9 @@ } }, "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, "supports-color": { "version": "7.1.0", @@ -2396,6 +2591,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": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", @@ -2428,13 +2665,13 @@ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" }, "espree": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", - "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.2.0.tgz", + "integrity": "sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g==", "requires": { - "acorn": "^7.2.0", + "acorn": "^7.3.1", "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.2.0" + "eslint-visitor-keys": "^1.3.0" } }, "esprima": { @@ -2518,11 +2755,6 @@ "homedir-polyfill": "^1.0.1" } }, - "expect-ct": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz", - "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==" - }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -2754,6 +2986,17 @@ } } }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -2796,6 +3039,59 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==" }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -2816,16 +3112,17 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, - "frameguard": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz", - "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==" - }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fromentries": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", + "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==", + "dev": true + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2898,6 +3195,12 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -3129,6 +3432,24 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, + "hasha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", + "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + } + } + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -3136,22 +3457,18 @@ "dev": true }, "helmet": { - "version": "3.21.2", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.21.2.tgz", - "integrity": "sha512-okUo+MeWgg00cKB8Csblu8EXgcIoDyb5ZS/3u0W4spCimeVuCUvVZ6Vj3O2VJ1Sxpyb8jCDvzu0L1KKT11pkIg==", + "version": "3.23.3", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz", + "integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==", "requires": { "depd": "2.0.0", - "dns-prefetch-control": "0.2.0", "dont-sniff-mimetype": "1.1.0", - "expect-ct": "0.2.0", "feature-policy": "0.3.0", - "frameguard": "3.1.0", "helmet-crossdomain": "0.4.0", - "helmet-csp": "2.9.4", + "helmet-csp": "2.10.0", "hide-powered-by": "1.1.0", "hpkp": "2.0.0", "hsts": "2.2.0", - "ienoopen": "1.1.0", "nocache": "2.1.0", "referrer-policy": "1.2.0", "x-xss-protection": "1.3.0" @@ -3170,11 +3487,11 @@ "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" }, "helmet-csp": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.9.4.tgz", - "integrity": "sha512-qUgGx8+yk7Xl8XFEGI4MFu1oNmulxhQVTlV8HP8tV3tpfslCs30OZz/9uQqsWPvDISiu/NwrrCowsZBhFADYqg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz", + "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==", "requires": { - "bowser": "^2.7.0", + "bowser": "2.9.0", "camelize": "1.0.0", "content-security-policy-builder": "2.1.0", "dasherize": "2.0.0" @@ -3228,6 +3545,12 @@ "whatwg-encoding": "^1.0.1" } }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "html-tags": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", @@ -3443,11 +3766,11 @@ } }, "iconv-lite": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", - "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "ieee754": { @@ -3456,11 +3779,6 @@ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", "dev": true }, - "ienoopen": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz", - "integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ==" - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -3710,6 +4028,12 @@ "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", "dev": true }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, "is-word-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", @@ -3731,6 +4055,168 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.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" + } + } + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3750,6 +4236,12 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "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": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", @@ -3785,9 +4277,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "ws": { @@ -4075,9 +4567,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash.find": { "version": "4.6.0", @@ -4085,6 +4577,12 @@ "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=", "dev": true }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -4212,6 +4710,23 @@ "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==", "dev": true }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", @@ -4749,9 +5264,9 @@ "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" }, "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" }, "mri": { "version": "1.1.5", @@ -4828,6 +5343,15 @@ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", "dev": true }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, "node-releases": { "version": "1.1.58", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz", @@ -4924,6 +5448,270 @@ "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.0.tgz", + "integrity": "sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^3.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "decamelize": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-3.2.0.tgz", + "integrity": "sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==", + "dev": true, + "requires": { + "xregexp": "^4.2.4" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -5106,12 +5894,33 @@ "p-limit": "^1.1.0" } }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5804,6 +6613,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -5993,6 +6811,21 @@ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", "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": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, "remark": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.0.tgz", @@ -6569,6 +7402,40 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -7713,6 +8580,33 @@ } } }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8304,6 +9198,15 @@ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" }, + "xregexp": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", + "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", + "dev": true, + "requires": { + "@babel/runtime-corejs3": "^7.8.3" + } + }, "xtend": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", diff --git a/package.json b/package.json index 5b9562d5..f2e95b7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "magicmirror", - "version": "2.12.0", + "version": "2.13.0-develop", "description": "The open source modular smart mirror platform.", "main": "js/electron.js", "scripts": { @@ -9,9 +9,10 @@ "install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error", "install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error", "postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"", - "test": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests --recursive", - "test:unit": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/unit --recursive", - "test:e2e": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/e2e --recursive", + "test": "NODE_ENV=test mocha tests --recursive", + "test:coverage": "NODE_ENV=test nyc mocha tests --recursive --timeout=3000", + "test:e2e": "NODE_ENV=test mocha tests/e2e --recursive", + "test:unit": "NODE_ENV=test mocha tests/unit --recursive", "test:prettier": "prettier --check **/*.{js,css,json,md,yml}", "test:js": "eslint *.js js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --quiet", "test:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json", @@ -47,6 +48,7 @@ "current-week-number": "^1.0.7", "danger": "^3.1.3", "eslint-config-prettier": "^6.11.0", + "eslint-plugin-jsdoc": "^30.1.0", "eslint-plugin-prettier": "^3.1.4", "http-auth": "^3.2.3", "husky": "^4.2.5", @@ -54,6 +56,7 @@ "mocha": "^7.1.2", "mocha-each": "^2.0.1", "mocha-logger": "^1.0.6", + "nyc": "^15.1.0", "prettier": "^2.0.5", "pretty-quick": "^2.0.1", "spectron": "^8.0.0", @@ -68,18 +71,18 @@ "dependencies": { "colors": "^1.1.2", "console-stamp": "^0.2.9", - "eslint": "^7.3.0", + "eslint": "^7.5.0", "express": "^4.16.2", "express-ipfilter": "^1.0.1", "feedme": "latest", - "helmet": "^3.21.2", + "helmet": "^3.23.3", "ical": "^0.8.0", "iconv-lite": "latest", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "module-alias": "^2.2.2", "moment": "latest", "request": "^2.88.2", - "rrule": "^2.6.2", + "rrule": "^2.6.4", "rrule-alt": "^2.2.8", "simple-git": "^1.85.0", "socket.io": "^2.1.1", diff --git a/tests/configs/data/StripComments.json b/tests/configs/data/StripComments.json deleted file mode 100644 index e9d1c403..00000000 --- a/tests/configs/data/StripComments.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - // Escaped - "FOO\"BAR": "Today", - - /* - * The following lines - * represent cardinal directions - */ - "N": "N", - "E": "E", - "S": "S", - "W": "W" -} diff --git a/tests/configs/data/calendar_test_icons.ics b/tests/configs/data/calendar_test_icons.ics new file mode 100644 index 00000000..7f24060d --- /dev/null +++ b/tests/configs/data/calendar_test_icons.ics @@ -0,0 +1,56 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//ical.marudot.com//iCal Event Maker +X-WR-CALNAME:TestEvents +NAME:TestEvents +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/Berlin +TZURL:http://tzurl.org/zoneinfo-outlook/Europe/Berlin +X-LIC-LOCATION:Europe/Berlin +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20200719T094531Z +UID:20200719T094531Z-1871115387@marudot.com +DTSTART;TZID=Europe/Berlin:20300101T120000 +DTEND;TZID=Europe/Berlin:20300101T130000 +SUMMARY:TestEvent +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20200719T094531Z +UID:20200719T094531Z-1929725136@marudot.com +DTSTART;TZID=Europe/Berlin:20300701T120000 +RRULE:FREQ=YEARLY;BYMONTH=7;BYMONTHDAY=1 +DTEND;TZID=Europe/Berlin:20300701T130000 +SUMMARY:TestEventRepeat +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20200719T094531Z +UID:20200719T094531Z-371801474@marudot.com +DTSTART;VALUE=DATE:20300401 +DTEND;VALUE=DATE:20300402 +SUMMARY:TestEventDay +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20200719T094531Z +UID:20200719T094531Z-133401084@marudot.com +DTSTART;VALUE=DATE:20301001 +RRULE:FREQ=YEARLY;BYMONTH=10;BYMONTHDAY=1 +DTEND;VALUE=DATE:20301002 +SUMMARY:TestEventRepeatDay +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/tests/configs/modules/calendar/custom.js b/tests/configs/modules/calendar/custom.js new file mode 100644 index 00000000..2084419d --- /dev/null +++ b/tests/configs/modules/calendar/custom.js @@ -0,0 +1,41 @@ +/* Magic Mirror Test config custom calendar + * + * MIT Licensed. + */ +let config = { + port: 8080, + ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], + + language: "en", + timeFormat: 12, + units: "metric", + electronOptions: { + webPreferences: { + nodeIntegration: true + } + }, + + modules: [ + { + module: "calendar", + position: "bottom_bar", + config: { + calendars: [ + { + symbol: "birthday-cake", + fullDaySymbol: "calendar-day", + recurringSymbol: "undo", + maximumEntries: 4, + maximumNumberOfDays: 10000, + url: "http://localhost:8080/tests/configs/data/calendar_test_icons.ics" + } + ] + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/tests/e2e/modules/calendar_spec.js b/tests/e2e/modules/calendar_spec.js index 7af78201..3712871c 100644 --- a/tests/e2e/modules/calendar_spec.js +++ b/tests/e2e/modules/calendar_spec.js @@ -1,5 +1,6 @@ const helpers = require("../global-setup"); const serverBasicAuth = require("../../servers/basic-auth.js"); +const expect = require("chai").expect; const describe = global.describe; const it = global.it; @@ -31,8 +32,47 @@ describe("Calendar module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/default.js"; }); - it("Should return TestEvents", function () { - return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); + it("should show the default maximumEntries of 10", async () => { + await app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); + const events = await app.client.$$(".calendar .event"); + return expect(events.length).equals(10); + }); + + it("should show the default calendar symbol in each event", async () => { + await app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); + const icons = await app.client.$$(".calendar .event .fa-calendar"); + return expect(icons.length).not.equals(0); + }); + }); + + describe("Custom configuration", function () { + before(function () { + // Set config sample for use in test + process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/custom.js"; + }); + + it("should show the custom maximumEntries of 4", async () => { + await app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); + const events = await app.client.$$(".calendar .event"); + return expect(events.length).equals(4); + }); + + it("should show the custom calendar symbol in each event", async () => { + await app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); + const icons = await app.client.$$(".calendar .event .fa-birthday-cake"); + return expect(icons.length).equals(4); + }); + + it("should show two custom icons for repeating events", async () => { + await app.client.waitUntilTextExists(".calendar", "TestEventRepeat", 10000); + const icons = await app.client.$$(".calendar .event .fa-undo"); + return expect(icons.length).equals(2); + }); + + it("should show two custom icons for day events", async () => { + await app.client.waitUntilTextExists(".calendar", "TestEventDay", 10000); + const icons = await app.client.$$(".calendar .event .fa-calendar-day"); + return expect(icons.length).equals(2); }); }); @@ -47,7 +87,7 @@ describe("Calendar module", function () { serverBasicAuth.close(done()); }); - it("Should return TestEvents", function () { + it("should return TestEvents", function () { return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); }); }); @@ -63,7 +103,7 @@ describe("Calendar module", function () { serverBasicAuth.close(done()); }); - it("Should return TestEvents", function () { + it("should return TestEvents", function () { return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); }); }); @@ -79,7 +119,7 @@ describe("Calendar module", function () { serverBasicAuth.close(done()); }); - it("Should return TestEvents", function () { + it("should return TestEvents", function () { return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); }); }); @@ -95,7 +135,7 @@ describe("Calendar module", function () { serverBasicAuth.close(done()); }); - it("Should return No upcoming events", function () { + it("should return No upcoming events", function () { return app.client.waitUntilTextExists(".calendar", "No upcoming events.", 10000); }); }); diff --git a/tests/servers/basic-auth.js b/tests/servers/basic-auth.js index b1277cf1..3c294a86 100644 --- a/tests/servers/basic-auth.js +++ b/tests/servers/basic-auth.js @@ -1,7 +1,7 @@ -var path = require("path"); -var auth = require("http-auth"); -var express = require("express"); -var app = express(); +const path = require("path"); +const auth = require("http-auth"); +const express = require("express"); +const app = express(); var server; diff --git a/tests/unit/classes/translator_spec.js b/tests/unit/classes/translator_spec.js index b0ec084c..e3355d68 100644 --- a/tests/unit/classes/translator_spec.js +++ b/tests/unit/classes/translator_spec.js @@ -171,25 +171,6 @@ describe("Translator", function () { }; }); - it("should strip comments", function (done) { - const dom = new JSDOM(`