Merge remote-tracking branch 'origin/develop' into Issue-TranslateEncodedHtml

This commit is contained in:
Dan Forsyth 2021-01-20 07:31:11 -05:00
commit ca48663efd
52 changed files with 2305 additions and 2293 deletions

View File

@ -23,9 +23,9 @@ If you are facing an issue or found a bug while running MagicMirror inside a Doc
Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line. Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line.
When submitting a new issue, please supply the following information: When submitting a new issue, please supply the following information:
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX). **Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
**Node Version**: Make sure it's version 8 or later. **Node Version**: Make sure it's version 10 or later.
**MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file. **MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file.

View File

@ -0,0 +1,24 @@
# This workflow runs the automated test and uploads the coverage results to codecov.io
name: "Run Codecov Tests"
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]
jobs:
run-and-upload-coverage-report:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99
npm ci
npm run test:coverage
- uses: codecov/codecov-action@v1
with:
file: ./coverage/lcov.info
fail_ci_if_error: true

View File

@ -1,10 +1,12 @@
# This workflow enforces the update of a changelog file on every pull request
name: "Enforce Changelog" name: "Enforce Changelog"
on: on:
pull_request: pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
jobs: jobs:
# Enforces the update of a changelog file on every pull request
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@ -1,7 +1,7 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Automated Tests name: "Run Automated Tests"
on: on:
push: push:
@ -11,13 +11,10 @@ on:
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [10.x, 12.x, 14.x] node-version: [10.x, 12.x, 14.x]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}

View File

@ -5,6 +5,32 @@ This project adheres to [Semantic Versioning](https://semver.org/).
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror² ❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
## [2.15.0] - Unreleased (Develop Branch)
_This release is scheduled to be released on 2021-04-01._
### Added
- Added GitHub workflows for automated testing and changelog enforcement.
- Add CodeCov badge to Readme.
### Updated
- Updated markdown files.
- Cleaned up old code on server side.
- Convert `-0` to `0` when displaying temperature.
- Code cleanup for FEELS like and added {DEGREE} placeholder for FEELSLIKE for each language
### Removed
- Removed danger.js library.
### Fixed
- Added default log levels to stop calendar log spamming.
- Fix socket.io cors errors, see [breaking change since socket.io v3](https://socket.io/docs/v3/handling-cors/)
- Fix Issue with weather forecast icons due to fixed day start and end time (#2221)
## [2.14.0] - 2021-01-01 ## [2.14.0] - 2021-01-01
Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank, @bluemanos, @flopp999, @jakemulley, @jakobsarwary1, @marvai-vgtu, @mirontoli, @rejas, @sdetweil, @Snille & @Sub028. Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank, @bluemanos, @flopp999, @jakemulley, @jakobsarwary1, @marvai-vgtu, @mirontoli, @rejas, @sdetweil, @Snille & @Sub028.
@ -25,7 +51,6 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Calendar: new options "limitDays" and "coloredEvents". - Calendar: new options "limitDays" and "coloredEvents".
- Added new option "limitDays" - limit the number of discreet days displayed. - Added new option "limitDays" - limit the number of discreet days displayed.
- Added new option "customEvents" - use custom symbol/color based on keyword in event title. - Added new option "customEvents" - use custom symbol/color based on keyword in event title.
- Added GitHub workflows for automated testing and changelog enforcement.
### Updated ### Updated

View File

@ -1,6 +1,6 @@
# The MIT License (MIT) # The MIT License (MIT)
Copyright © 2016-2020 Michael Teeuw Copyright © 2016-2021 Michael Teeuw
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation

View File

@ -1,9 +1,10 @@
![MagicMirror²: The open source modular smart mirror platform. ](.github/header.png) ![MagicMirror²: The open source modular smart mirror platform. ](.github/header.png)
<p align="center"> <p style="text-align: center">
<a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a> <a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a>
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a> <a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a> <a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge" alt="CLI Best Practices"></a>
<a href="https://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg?token=LEG1KitZR6"/></a>
<a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a> <a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<a href="https://github.com/MichMich/MagicMirror/actions?query=workflow%3A%22Automated+Tests%22"><img src="https://github.com/MichMich/MagicMirror/workflows/Automated%20Tests/badge.svg" alt="Tests"></a> <a href="https://github.com/MichMich/MagicMirror/actions?query=workflow%3A%22Automated+Tests%22"><img src="https://github.com/MichMich/MagicMirror/workflows/Automated%20Tests/badge.svg" alt="Tests"></a>
</p> </p>
@ -38,7 +39,6 @@ If we receive enough donations we might even be able to free up some working hou
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link. To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
<p align="center"> <p style="text-align: center">
<br>
<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a> <a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>
</p> </p>

View File

@ -1,17 +0,0 @@
import { danger, fail, warn } from "danger";
// Check if the CHANGELOG.md file has been edited
// Fail the build and post a comment reminding submitters to do so if it wasn't changed
if (!danger.git.modified_files.includes("CHANGELOG.md")) {
warn("Please include an updated `CHANGELOG.md` file.<br>This way we can keep track of all the contributions.");
}
// Check if the PR request is send to the master branch.
// This should only be done by MichMich.
if (danger.github.pr.base.ref === "master" && danger.github.pr.user.login !== "MichMich") {
// Check if the PR body or title includes the text: #accepted.
// If not, the PR will fail.
if ((danger.github.pr.body + danger.github.pr.title).includes("#accepted")) {
fail("Please send all your pull requests to the `develop` branch.<br>Pull requests on the `master` branch will not be accepted.");
}
}

126
js/app.js
View File

@ -4,22 +4,22 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var fs = require("fs"); const fs = require("fs");
var path = require("path"); const path = require("path");
var Log = require(__dirname + "/logger.js"); const Log = require(`${__dirname}/logger`);
var Server = require(__dirname + "/server.js"); const Server = require(`${__dirname}/server`);
var Utils = require(__dirname + "/utils.js"); const Utils = require(`${__dirname}/utils`);
var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js"); const defaultModules = require(`${__dirname}/../modules/default/defaultmodules`);
// Alias modules mentioned in package.js under _moduleAliases. // Alias modules mentioned in package.js under _moduleAliases.
require("module-alias/register"); require("module-alias/register");
// Get version number. // Get version number.
global.version = JSON.parse(fs.readFileSync("package.json", "utf8")).version; global.version = require(`${__dirname}/../package.json`).version;
Log.log("Starting MagicMirror: v" + global.version); Log.log("Starting MagicMirror: v" + global.version);
// global absolute root path // global absolute root path
global.root_path = path.resolve(__dirname + "/../"); global.root_path = path.resolve(`${__dirname}/../`);
if (process.env.MM_CONFIG_FILE) { if (process.env.MM_CONFIG_FILE) {
global.configuration_file = process.env.MM_CONFIG_FILE; global.configuration_file = process.env.MM_CONFIG_FILE;
@ -45,8 +45,8 @@ process.on("uncaughtException", function (err) {
* *
* @class * @class
*/ */
var App = function () { function App() {
var nodeHelpers = []; let nodeHelpers = [];
/** /**
* Loads the config file. Combines it with the defaults, and runs the * Loads the config file. Combines it with the defaults, and runs the
@ -54,34 +54,31 @@ var App = function () {
* *
* @param {Function} callback Function to be called after loading the config * @param {Function} callback Function to be called after loading the config
*/ */
var loadConfig = function (callback) { function loadConfig(callback) {
Log.log("Loading config ..."); Log.log("Loading config ...");
var defaults = require(__dirname + "/defaults.js"); const defaults = require(`${__dirname}/defaults`);
// For this check proposed to TestSuite // For this check proposed to TestSuite
// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8 // https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
var configFilename = path.resolve(global.root_path + "/config/config.js"); const configFilename = path.resolve(global.configuration_file || `${global.root_path}/config/config.js`);
if (typeof global.configuration_file !== "undefined") {
configFilename = path.resolve(global.configuration_file);
}
try { try {
fs.accessSync(configFilename, fs.F_OK); fs.accessSync(configFilename, fs.F_OK);
var c = require(configFilename); const c = require(configFilename);
checkDeprecatedOptions(c); checkDeprecatedOptions(c);
var config = Object.assign(defaults, c); const config = Object.assign(defaults, c);
callback(config); callback(config);
} catch (e) { } catch (e) {
if (e.code === "ENOENT") { if (e.code === "ENOENT") {
Log.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration.")); Log.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration."));
} else if (e instanceof ReferenceError || e instanceof SyntaxError) { } else if (e instanceof ReferenceError || e instanceof SyntaxError) {
Log.error(Utils.colors.error("WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: " + e.stack)); Log.error(Utils.colors.error(`WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: ${e.stack}`));
} else { } else {
Log.error(Utils.colors.error("WARNING! Could not load config file. Starting with default configuration. Error found: " + e)); Log.error(Utils.colors.error(`WARNING! Could not load config file. Starting with default configuration. Error found: ${e}`));
} }
callback(defaults); callback(defaults);
} }
}; }
/** /**
* Checks the config for deprecated options and throws a warning in the logs * Checks the config for deprecated options and throws a warning in the logs
@ -89,21 +86,15 @@ var App = function () {
* *
* @param {object} userConfig The user config * @param {object} userConfig The user config
*/ */
var checkDeprecatedOptions = function (userConfig) { function checkDeprecatedOptions(userConfig) {
var deprecated = require(global.root_path + "/js/deprecated.js"); const deprecated = require(`${global.root_path}/js/deprecated`);
var deprecatedOptions = deprecated.configs; const deprecatedOptions = deprecated.configs;
var usedDeprecated = []; const usedDeprecated = deprecatedOptions.filter((option) => userConfig.hasOwnProperty(option));
deprecatedOptions.forEach(function (option) {
if (userConfig.hasOwnProperty(option)) {
usedDeprecated.push(option);
}
});
if (usedDeprecated.length > 0) { if (usedDeprecated.length > 0) {
Log.warn(Utils.colors.warn("WARNING! Your config is using deprecated options: " + usedDeprecated.join(", ") + ". Check README and CHANGELOG for more up-to-date ways of getting the same functionality.")); Log.warn(Utils.colors.warn(`WARNING! Your config is using deprecated options: ${usedDeprecated.join(", ")}. Check README and CHANGELOG for more up-to-date ways of getting the same functionality.`));
} }
}; }
/** /**
* Loads a specific module. * Loads a specific module.
@ -111,35 +102,35 @@ var App = function () {
* @param {string} module 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 * @param {Function} callback Function to be called after loading
*/ */
var loadModule = function (module, callback) { function loadModule(module, callback) {
var elements = module.split("/"); const elements = module.split("/");
var moduleName = elements[elements.length - 1]; const moduleName = elements[elements.length - 1];
var moduleFolder = __dirname + "/../modules/" + module; let moduleFolder = `${__dirname}/../modules/${module}`;
if (defaultModules.indexOf(moduleName) !== -1) { if (defaultModules.includes(moduleName)) {
moduleFolder = __dirname + "/../modules/default/" + module; moduleFolder = `${__dirname}/../modules/default/${module}`;
} }
var helperPath = moduleFolder + "/node_helper.js"; const helperPath = `${moduleFolder}/node_helper.js`;
var loadModule = true; let loadHelper = true;
try { try {
fs.accessSync(helperPath, fs.R_OK); fs.accessSync(helperPath, fs.R_OK);
} catch (e) { } catch (e) {
loadModule = false; loadHelper = false;
Log.log("No helper found for module: " + moduleName + "."); Log.log(`No helper found for module: ${moduleName}.`);
} }
if (loadModule) { if (loadHelper) {
var Module = require(helperPath); const Module = require(helperPath);
var m = new Module(); let m = new Module();
if (m.requiresVersion) { if (m.requiresVersion) {
Log.log("Check MagicMirror version for node helper '" + moduleName + "' - Minimum version: " + m.requiresVersion + " - Current version: " + global.version); Log.log(`Check MagicMirror version for node helper '${moduleName}' - Minimum version: ${m.requiresVersion} - Current version: ${global.version}`);
if (cmpVersions(global.version, m.requiresVersion) >= 0) { if (cmpVersions(global.version, m.requiresVersion) >= 0) {
Log.log("Version is ok!"); Log.log("Version is ok!");
} else { } else {
Log.log("Version is incorrect. Skip module: '" + moduleName + "'"); Log.warn(`Version is incorrect. Skip module: '${moduleName}'`);
return; return;
} }
} }
@ -152,7 +143,7 @@ var App = function () {
} else { } else {
callback(); callback();
} }
}; }
/** /**
* Loads all modules. * Loads all modules.
@ -160,12 +151,12 @@ var App = function () {
* @param {Module[]} modules All modules to be loaded * @param {Module[]} modules All modules to be loaded
* @param {Function} callback Function to be called after loading * @param {Function} callback Function to be called after loading
*/ */
var loadModules = function (modules, callback) { function loadModules(modules, callback) {
Log.log("Loading module helpers ..."); Log.log("Loading module helpers ...");
var loadNextModule = function () { function loadNextModule() {
if (modules.length > 0) { if (modules.length > 0) {
var nextModule = modules[0]; const nextModule = modules[0];
loadModule(nextModule, function () { loadModule(nextModule, function () {
modules = modules.slice(1); modules = modules.slice(1);
loadNextModule(); loadNextModule();
@ -175,10 +166,10 @@ var App = function () {
Log.log("All module helpers loaded."); Log.log("All module helpers loaded.");
callback(); callback();
} }
}; }
loadNextModule(); loadNextModule();
}; }
/** /**
* Compare two semantic version numbers and return the difference. * Compare two semantic version numbers and return the difference.
@ -190,11 +181,11 @@ var App = function () {
* number if a is smaller and 0 if they are the same * number if a is smaller and 0 if they are the same
*/ */
function cmpVersions(a, b) { function cmpVersions(a, b) {
var i, diff; let i, diff;
var regExStrip0 = /(\.0+)+$/; const regExStrip0 = /(\.0+)+$/;
var segmentsA = a.replace(regExStrip0, "").split("."); const segmentsA = a.replace(regExStrip0, "").split(".");
var segmentsB = b.replace(regExStrip0, "").split("."); const segmentsB = b.replace(regExStrip0, "").split(".");
var l = Math.min(segmentsA.length, segmentsB.length); const l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) { for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10); diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
@ -219,21 +210,19 @@ var App = function () {
Log.setLogLevel(config.logLevel); Log.setLogLevel(config.logLevel);
var modules = []; let modules = [];
for (var m in config.modules) { for (const module of config.modules) {
var module = config.modules[m]; if (!modules.includes(module.module) && !module.disabled) {
if (modules.indexOf(module.module) === -1 && !module.disabled) {
modules.push(module.module); modules.push(module.module);
} }
} }
loadModules(modules, function () { loadModules(modules, function () {
var server = new Server(config, function (app, io) { const server = new Server(config, function (app, io) {
Log.log("Server started ..."); Log.log("Server started ...");
for (var h in nodeHelpers) { for (let nodeHelper of nodeHelpers) {
var nodeHelper = nodeHelpers[h];
nodeHelper.setExpressApp(app); nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io); nodeHelper.setSocketIO(io);
nodeHelper.start(); nodeHelper.start();
@ -256,8 +245,7 @@ var App = function () {
* Added to fix #1056 * Added to fix #1056
*/ */
this.stop = function () { this.stop = function () {
for (var h in nodeHelpers) { for (const nodeHelper of nodeHelpers) {
var nodeHelper = nodeHelpers[h];
if (typeof nodeHelper.stop === "function") { if (typeof nodeHelper.stop === "function") {
nodeHelper.stop(); nodeHelper.stop();
} }
@ -292,6 +280,6 @@ var App = function () {
this.stop(); this.stop();
process.exit(0); process.exit(0);
}); });
}; }
module.exports = new App(); module.exports = new App();

View File

@ -11,9 +11,9 @@ const linter = new Linter();
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs");
const rootPath = path.resolve(__dirname + "/../"); const rootPath = path.resolve(`${__dirname}/../`);
const Log = require(rootPath + "/js/logger.js"); const Log = require(`${rootPath}/js/logger.js`);
const Utils = require(rootPath + "/js/utils.js"); const Utils = require(`${rootPath}/js/utils.js`);
/** /**
* Returns a string with path of configuration file. * Returns a string with path of configuration file.
@ -23,11 +23,7 @@ const Utils = require(rootPath + "/js/utils.js");
*/ */
function getConfigFile() { function getConfigFile() {
// FIXME: This function should be in core. Do you want refactor me ;) ?, be good! // FIXME: This function should be in core. Do you want refactor me ;) ?, be good!
let configFileName = path.resolve(rootPath + "/config/config.js"); return path.resolve(process.env.MM_CONFIG_FILE || `${rootPath}/config/config.js`);
if (process.env.MM_CONFIG_FILE) {
configFileName = path.resolve(process.env.MM_CONFIG_FILE);
}
return configFileName;
} }
/** /**
@ -54,21 +50,18 @@ function checkConfigFile() {
Log.info(Utils.colors.info("Checking file... "), configFileName); Log.info(Utils.colors.info("Checking file... "), configFileName);
// I'm not sure if all ever is utf-8 // I'm not sure if all ever is utf-8
fs.readFile(configFileName, "utf-8", function (err, data) { const configFile = fs.readFileSync(configFileName, "utf-8");
if (err) {
throw err; const errors = linter.verify(configFile);
if (errors.length === 0) {
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 :("));
for (const error of errors) {
Log.error(`Line ${error.line} column ${error.column}: ${error.message}`);
} }
const messages = linter.verify(data); }
if (messages.length === 0) {
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.error("Line", error.line, "col", error.column, error.message);
});
}
});
} }
checkConfigFile(); checkConfigFile();

View File

@ -20,6 +20,7 @@ var defaults = {
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en", language: "en",
logLevel: ["INFO", "LOG", "WARN", "ERROR"],
timeFormat: 24, timeFormat: 24,
units: "metric", units: "metric",
zoom: 1, zoom: 1,

View File

@ -6,11 +6,6 @@
* Olex S. original idea this deprecated option * Olex S. original idea this deprecated option
*/ */
var deprecated = { module.exports = {
configs: ["kioskmode"] configs: ["kioskmode"]
}; };
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = deprecated;
}

View File

@ -5,7 +5,7 @@ const core = require("./app.js");
const Log = require("./logger.js"); const Log = require("./logger.js");
// Config // Config
var config = process.env.config ? JSON.parse(process.env.config) : {}; let config = process.env.config ? JSON.parse(process.env.config) : {};
// Module to control application life. // Module to control application life.
const app = electron.app; const app = electron.app;
// Module to create native browser window. // Module to create native browser window.
@ -20,7 +20,7 @@ let mainWindow;
*/ */
function createWindow() { function createWindow() {
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
var electronOptionsDefaults = { let electronOptionsDefaults = {
width: 800, width: 800,
height: 600, height: 600,
x: 0, x: 0,
@ -42,7 +42,7 @@ function createWindow() {
electronOptionsDefaults.autoHideMenuBar = true; electronOptionsDefaults.autoHideMenuBar = true;
} }
var electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions); const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
// Create the browser window. // Create the browser window.
mainWindow = new BrowserWindow(electronOptions); mainWindow = new BrowserWindow(electronOptions);
@ -50,14 +50,14 @@ function createWindow() {
// and load the index.html of the app. // and load the index.html of the app.
// If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost // If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost
var prefix; let prefix;
if (config["tls"] !== null && config["tls"]) { if (config["tls"] !== null && config["tls"]) {
prefix = "https://"; prefix = "https://";
} else { } else {
prefix = "http://"; prefix = "http://";
} }
var address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address; let address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address;
mainWindow.loadURL(`${prefix}${address}:${config.port}`); mainWindow.loadURL(`${prefix}${address}:${config.port}`);
// Open the DevTools if run with "npm start dev" // Open the DevTools if run with "npm start dev"
@ -125,7 +125,7 @@ app.on("before-quit", (event) => {
// Start the core application if server is run on localhost // Start the core application if server is run on localhost
// This starts all node helpers and starts the webserver. // This starts all node helpers and starts the webserver.
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) > -1) { if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(config.address)) {
core.start(function (c) { core.start(function (c) {
config = c; config = c;
}); });

View File

@ -8,18 +8,18 @@ const Class = require("./class.js");
const Log = require("./logger.js"); const Log = require("./logger.js");
const express = require("express"); const express = require("express");
var NodeHelper = Class.extend({ const NodeHelper = Class.extend({
init: function () { init() {
Log.log("Initializing new module helper ..."); Log.log("Initializing new module helper ...");
}, },
loaded: function (callback) { loaded(callback) {
Log.log("Module helper loaded: " + this.name); Log.log(`Module helper loaded: ${this.name}`);
callback(); callback();
}, },
start: function () { start() {
Log.log("Starting module helper: " + this.name); Log.log(`Starting module helper: ${this.name}`);
}, },
/* stop() /* stop()
@ -28,8 +28,8 @@ var NodeHelper = Class.extend({
* gracefully exit the module. * gracefully exit the module.
* *
*/ */
stop: function () { stop() {
Log.log("Stopping module helper: " + this.name); Log.log(`Stopping module helper: ${this.name}`);
}, },
/* socketNotificationReceived(notification, payload) /* socketNotificationReceived(notification, payload)
@ -38,8 +38,8 @@ var NodeHelper = Class.extend({
* argument notification string - The identifier of the notification. * argument notification string - The identifier of the notification.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
socketNotificationReceived: function (notification, payload) { socketNotificationReceived(notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload); Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`);
}, },
/* setName(name) /* setName(name)
@ -47,7 +47,7 @@ var NodeHelper = Class.extend({
* *
* argument name string - Module name. * argument name string - Module name.
*/ */
setName: function (name) { setName(name) {
this.name = name; this.name = name;
}, },
@ -56,7 +56,7 @@ var NodeHelper = Class.extend({
* *
* argument path string - Module path. * argument path string - Module path.
*/ */
setPath: function (path) { setPath(path) {
this.path = path; this.path = path;
}, },
@ -66,7 +66,7 @@ var NodeHelper = Class.extend({
* argument notification string - The identifier of the notification. * argument notification string - The identifier of the notification.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
sendSocketNotification: function (notification, payload) { sendSocketNotification(notification, payload) {
this.io.of(this.name).emit(notification, payload); this.io.of(this.name).emit(notification, payload);
}, },
@ -76,11 +76,10 @@ var NodeHelper = Class.extend({
* *
* argument app Express app - The Express app object. * argument app Express app - The Express app object.
*/ */
setExpressApp: function (app) { setExpressApp(app) {
this.expressApp = app; this.expressApp = app;
var publicPath = this.path + "/public"; app.use(`/${this.name}`, express.static(`${this.path}/public`));
app.use("/" + this.name, express.static(publicPath));
}, },
/* setSocketIO(io) /* setSocketIO(io)
@ -89,27 +88,25 @@ var NodeHelper = Class.extend({
* *
* argument io Socket.io - The Socket io object. * argument io Socket.io - The Socket io object.
*/ */
setSocketIO: function (io) { setSocketIO(io) {
var self = this; this.io = io;
self.io = io;
Log.log("Connecting socket for: " + this.name); Log.log(`Connecting socket for: ${this.name}`);
var namespace = this.name;
io.of(namespace).on("connection", function (socket) { io.of(this.name).on("connection", (socket) => {
// add a catch all event. // add a catch all event.
var onevent = socket.onevent; const onevent = socket.onevent;
socket.onevent = function (packet) { socket.onevent = function (packet) {
var args = packet.data || []; const args = packet.data || [];
onevent.call(this, packet); // original call onevent.call(this, packet); // original call
packet.data = ["*"].concat(args); packet.data = ["*"].concat(args);
onevent.call(this, packet); // additional call to catch-all onevent.call(this, packet); // additional call to catch-all
}; };
// register catch all. // register catch all.
socket.on("*", function (notification, payload) { socket.on("*", (notification, payload) => {
if (notification !== "*") { if (notification !== "*") {
//Log.log('received message in namespace: ' + namespace); this.socketNotificationReceived(notification, payload);
self.socketNotificationReceived(notification, payload);
} }
}); });
}); });
@ -120,7 +117,4 @@ NodeHelper.create = function (moduleDefinition) {
return NodeHelper.extend(moduleDefinition); return NodeHelper.extend(moduleDefinition);
}; };
/*************** DO NOT EDIT THE LINE BELOW ***************/ module.exports = NodeHelper;
if (typeof module !== "undefined") {
module.exports = NodeHelper;
}

View File

@ -4,25 +4,22 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var express = require("express"); const express = require("express");
var app = require("express")(); const app = require("express")();
var path = require("path"); const path = require("path");
var ipfilter = require("express-ipfilter").IpFilter; const ipfilter = require("express-ipfilter").IpFilter;
var fs = require("fs"); const fs = require("fs");
var helmet = require("helmet"); const helmet = require("helmet");
var Log = require("./logger.js"); const Log = require("./logger.js");
var Utils = require("./utils.js"); const Utils = require("./utils.js");
var Server = function (config, callback) { function Server(config, callback) {
var port = config.port; const port = process.env.MM_PORT || config.port;
if (process.env.MM_PORT) {
port = process.env.MM_PORT;
}
var server = null; let server = null;
if (config.useHttps) { if (config.useHttps) {
var options = { const options = {
key: fs.readFileSync(config.httpsPrivateKey), key: fs.readFileSync(config.httpsPrivateKey),
cert: fs.readFileSync(config.httpsCertificate) cert: fs.readFileSync(config.httpsCertificate)
}; };
@ -30,18 +27,20 @@ var Server = function (config, callback) {
} else { } else {
server = require("http").Server(app); server = require("http").Server(app);
} }
var io = require("socket.io")(server); const io = require("socket.io")(server, {
cors: {}
});
Log.log("Starting server on port " + port + " ... "); Log.log(`Starting server on port ${port} ... `);
server.listen(port, config.address ? config.address : "localhost"); server.listen(port, config.address || "localhost");
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) { if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs")); Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
} }
app.use(function (req, res, next) { app.use(function (req, res, next) {
var result = ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) { ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
if (err === undefined) { if (err === undefined) {
return next(); return next();
} }
@ -52,10 +51,9 @@ var Server = function (config, callback) {
app.use(helmet({ contentSecurityPolicy: false })); app.use(helmet({ contentSecurityPolicy: false }));
app.use("/js", express.static(__dirname)); app.use("/js", express.static(__dirname));
var directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"];
var directory; const directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"];
for (var i in directories) { for (const directory of directories) {
directory = directories[i];
app.use(directory, express.static(path.resolve(global.root_path + directory))); app.use(directory, express.static(path.resolve(global.root_path + directory)));
} }
@ -68,10 +66,10 @@ var Server = function (config, callback) {
}); });
app.get("/", function (req, res) { app.get("/", function (req, res) {
var html = fs.readFileSync(path.resolve(global.root_path + "/index.html"), { encoding: "utf8" }); let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
html = html.replace("#VERSION#", global.version); html = html.replace("#VERSION#", global.version);
var configFile = "config/config.js"; let configFile = "config/config.js";
if (typeof global.configuration_file !== "undefined") { if (typeof global.configuration_file !== "undefined") {
configFile = global.configuration_file; configFile = global.configuration_file;
} }
@ -83,6 +81,6 @@ var Server = function (config, callback) {
if (typeof callback === "function") { if (typeof callback === "function") {
callback(app, io); callback(app, io);
} }
}; }
module.exports = Server; module.exports = Server;

View File

@ -4,9 +4,9 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
var colors = require("colors/safe"); const colors = require("colors/safe");
var Utils = { module.exports = {
colors: { colors: {
warn: colors.yellow, warn: colors.yellow,
error: colors.red, error: colors.red,
@ -14,7 +14,3 @@ var Utils = {
pass: colors.green pass: colors.green
} }
}; };
if (typeof module !== "undefined") {
module.exports = Utils;
}

View File

@ -258,13 +258,9 @@ Module.register("currentweather", {
var feelsLike = document.createElement("span"); var feelsLike = document.createElement("span");
feelsLike.className = "dimmed"; feelsLike.className = "dimmed";
var feelsLikeHtml = this.translate("FEELS"); feelsLike.innerHTML = this.translate("FEELS", {
if (feelsLikeHtml.indexOf("{DEGREE}") > -1) { DEGREE: this.feelsLike + degreeLabel
feelsLikeHtml = this.translate("FEELS", { });
DEGREE: this.feelsLike + degreeLabel
});
} else feelsLikeHtml += " " + this.feelsLike + degreeLabel;
feelsLike.innerHTML = feelsLikeHtml;
small.appendChild(feelsLike); small.appendChild(feelsLike);
wrapper.appendChild(small); wrapper.appendChild(small);
@ -593,6 +589,7 @@ Module.register("currentweather", {
*/ */
roundValue: function (temperature) { roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1; var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals); var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
} }
}); });

View File

@ -70,9 +70,6 @@
{% if config.showFeelsLike %} {% if config.showFeelsLike %}
<span class="dimmed"> <span class="dimmed">
{{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }} {{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }}
{% if not config.feelsLikeWithDegree %}
{{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }}
{% endif %}
</span> </span>
{% endif %} {% endif %}
{% if config.showPrecipitationAmount %} {% if config.showPrecipitationAmount %}

View File

@ -52,8 +52,7 @@ Module.register("weather", {
onlyTemp: false, onlyTemp: false,
showPrecipitationAmount: false, showPrecipitationAmount: false,
colored: false, colored: false,
showFeelsLike: true, showFeelsLike: true
feelsLikeWithDegree: false
}, },
// Module properties. // Module properties.
@ -89,8 +88,6 @@ Module.register("weather", {
// Let the weather provider know we are starting. // Let the weather provider know we are starting.
this.weatherProvider.start(); this.weatherProvider.start();
this.config.feelsLikeWithDegree = this.translate("FEELS").indexOf("{DEGREE}") > -1;
// Add custom filters // Add custom filters
this.addFilters(); this.addFilters();
@ -178,7 +175,8 @@ Module.register("weather", {
roundValue: function (temperature) { roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1; var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals); var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
}, },
addFilters() { addFilters() {

View File

@ -351,6 +351,13 @@ Module.register("weatherforecast", {
this.forecast = []; this.forecast = [];
var lastDay = null; var lastDay = null;
var forecastData = {}; var forecastData = {};
var dayStarts = 8;
var dayEnds = 17;
if (data.city && data.city.sunrise && data.city.sunset) {
dayStarts = moment.unix(data.city.sunrise).toDate().getHours();
dayEnds = moment.unix(data.city.sunset).toDate().getHours();
}
// Handle different structs between forecast16 and onecall endpoints // Handle different structs between forecast16 and onecall endpoints
var forecastList = null; var forecastList = null;
@ -400,7 +407,7 @@ Module.register("weatherforecast", {
// Since we don't want an icon from the start of the day (in the middle of the night) // Since we don't want an icon from the start of the day (in the middle of the night)
// we update the icon as long as it's somewhere during the day. // we update the icon as long as it's somewhere during the day.
if (hour >= 8 && hour <= 17) { if (hour > dayStarts && hour < dayEnds) {
forecastData.icon = this.config.iconTable[forecast.weather[0].icon]; forecastData.icon = this.config.iconTable[forecast.weather[0].icon];
} }
} }
@ -462,7 +469,8 @@ Module.register("weatherforecast", {
*/ */
roundValue: function (temperature) { roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1; var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals); var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
}, },
/* processRain(forecast, allForecasts) /* processRain(forecast, allForecasts)

2189
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "magicmirror", "name": "magicmirror",
"version": "2.14.0", "version": "2.15.0-develop",
"description": "The open source modular smart mirror platform.", "description": "The open source modular smart mirror platform.",
"main": "js/electron.js", "main": "js/electron.js",
"scripts": { "scripts": {
@ -10,11 +10,11 @@
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && 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\"", "postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"",
"test": "NODE_ENV=test mocha tests --recursive", "test": "NODE_ENV=test mocha tests --recursive",
"test:coverage": "NODE_ENV=test nyc mocha tests --recursive --timeout=3000", "test:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text mocha tests --recursive --timeout=3000",
"test:e2e": "NODE_ENV=test mocha tests/e2e --recursive", "test:e2e": "NODE_ENV=test mocha tests/e2e --recursive",
"test:unit": "NODE_ENV=test mocha tests/unit --recursive", "test:unit": "NODE_ENV=test mocha tests/unit --recursive",
"test:prettier": "prettier --check **/*.{js,css,json,md,yml}", "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:js": "eslint 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", "test:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json",
"test:calendar": "node ./modules/default/calendar/debug.js", "test:calendar": "node ./modules/default/calendar/debug.js",
"config:check": "node js/check_config.js", "config:check": "node js/check_config.js",
@ -44,12 +44,11 @@
"devDependencies": { "devDependencies": {
"chai": "^4.2.0", "chai": "^4.2.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"danger": "^10.5.4", "eslint-config-prettier": "^7.1.0",
"eslint-config-prettier": "^7.0.0", "eslint-plugin-jsdoc": "^30.7.13",
"eslint-plugin-jsdoc": "^30.7.8", "eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-prettier": "^3.2.0",
"express-basic-auth": "^1.2.0", "express-basic-auth": "^1.2.0",
"husky": "^4.3.5", "husky": "^4.3.6",
"jsdom": "^16.4.0", "jsdom": "^16.4.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"mocha": "^8.2.1", "mocha": "^8.2.1",
@ -70,11 +69,11 @@
"dependencies": { "dependencies": {
"colors": "^1.4.0", "colors": "^1.4.0",
"console-stamp": "^3.0.0-rc4.2", "console-stamp": "^3.0.0-rc4.2",
"eslint": "^7.15.0", "eslint": "^7.17.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-ipfilter": "^1.1.2", "express-ipfilter": "^1.1.2",
"feedme": "^2.0.2", "feedme": "^2.0.2",
"helmet": "^4.2.0", "helmet": "^4.3.1",
"ical": "^0.8.0", "ical": "^0.8.0",
"iconv-lite": "^0.6.2", "iconv-lite": "^0.6.2",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
@ -84,12 +83,15 @@
"rrule": "^2.6.6", "rrule": "^2.6.6",
"rrule-alt": "^2.2.8", "rrule-alt": "^2.2.8",
"simple-git": "^2.31.0", "simple-git": "^2.31.0",
"socket.io": "^3.0.4", "socket.io": "^3.0.5",
"valid-url": "^1.0.9" "valid-url": "^1.0.9"
}, },
"_moduleAliases": { "_moduleAliases": {
"node_helper": "js/node_helper.js" "node_helper": "js/node_helper.js"
}, },
"engines": {
"node": ">=10"
},
"husky": { "husky": {
"hooks": { "hooks": {
"pre-commit": "pretty-quick --staged" "pre-commit": "pretty-quick --staged"

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
/* eslint no-multi-spaces: 0 */ /* eslint no-multi-spaces: 0 */
const expect = require("chai").expect; const expect = require("chai").expect;
var data = require("../functions/weatherforecast_data.json");
describe("Functions module weatherforecast", function () { describe("Functions module weatherforecast", function () {
before(function () { before(function () {
@ -63,4 +64,38 @@ describe("Functions module weatherforecast", function () {
}); });
}); });
}); });
describe("forecastIcons", function () {
Log = {
error: function () {}
};
describe("forecastIcons sunset specified", function () {
before(function () {
Module.definitions.weatherforecast.Log = {};
Module.definitions.weatherforecast.forecast = [];
Module.definitions.weatherforecast.show = Module.definitions.weatherforecast.updateDom = function () {};
Module.definitions.weatherforecast.config = Module.definitions.weatherforecast.defaults;
});
it(`returns correct icons with sunset time`, function () {
Module.definitions.weatherforecast.processWeather(data.withSunset);
let forecastData = Module.definitions.weatherforecast.forecast;
expect(forecastData.length).to.equal(4);
expect(forecastData[2].icon).to.equal("wi-rain");
});
});
describe("forecastIcons sunset not specified", function () {
before(function () {
Module.definitions.weatherforecast.forecast = [];
});
it(`returns correct icons with out sunset time`, function () {
Module.definitions.weatherforecast.processWeather(data.withoutSunset);
let forecastData = Module.definitions.weatherforecast.forecast;
expect(forecastData.length).to.equal(4);
expect(forecastData[2].icon).to.equal("wi-rain");
});
});
});
}); });

View File

@ -3,12 +3,12 @@ const path = require("path");
const expect = require("chai").expect; const expect = require("chai").expect;
const vm = require("vm"); const vm = require("vm");
before(function () { const basedir = path.join(__dirname, "../../..");
var basedir = path.join(__dirname, "../../..");
var fileName = "js/app.js"; before(function () {
var filePath = path.join(basedir, fileName); const fileName = "js/app.js";
var code = fs.readFileSync(filePath); const filePath = path.join(basedir, fileName);
const code = fs.readFileSync(filePath);
this.sandbox = { this.sandbox = {
module: {}, module: {},
@ -36,22 +36,12 @@ before(function () {
vm.runInNewContext(code, this.sandbox, fileName); vm.runInNewContext(code, this.sandbox, fileName);
}); });
after(function () {
//console.log(global);
});
describe("Default modules set in modules/default/defaultmodules.js", function () { describe("Default modules set in modules/default/defaultmodules.js", function () {
var expectedDefaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification"]; const expectedDefaultModules = require("../../../modules/default/defaultmodules");
expectedDefaultModules.forEach((defaultModule) => { for (const defaultModule of expectedDefaultModules) {
it(`contains default module "${defaultModule}"`, function () {
expect(this.sandbox.defaultModules).to.include(defaultModule);
});
});
expectedDefaultModules.forEach((defaultModule) => {
it(`contains a folder for modules/default/${defaultModule}"`, function () { it(`contains a folder for modules/default/${defaultModule}"`, function () {
expect(fs.existsSync(path.join(this.sandbox.global.root_path, "modules/default", defaultModule))).to.equal(true); expect(fs.existsSync(path.join(this.sandbox.global.root_path, "modules/default", defaultModule))).to.equal(true);
}); });
}); }
}); });

View File

@ -25,7 +25,7 @@
"WNW": "АҪА", "WNW": "АҪА",
"NW": "ҪА", "NW": "ҪА",
"NNW": "ҪҪА", "NNW": "ҪҪА",
"FEELS": "Туйӑннӑ", "FEELS": "Туйӑннӑ {DEGREE}",
"UPDATE_NOTIFICATION": "MagicMirror² валли ҫӗнетӳ пур.", "UPDATE_NOTIFICATION": "MagicMirror² валли ҫӗнетӳ пур.",
"UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME} модуль валли ҫӗнетӳ пур.", "UPDATE_NOTIFICATION_MODULE": "{MODULE_NAME} модуль валли ҫӗнетӳ пур.",

View File

@ -6,7 +6,7 @@
"DAYAFTERTOMORROW": "I overmorgen", "DAYAFTERTOMORROW": "I overmorgen",
"RUNNING": "Slutter om", "RUNNING": "Slutter om",
"EMPTY": "Ingen kommende begivenheder.", "EMPTY": "Ingen kommende begivenheder.",
"FEELS": "Føles som", "FEELS": "Føles som {DEGREE}",
"WEEK": "Uge {weekNumber}", "WEEK": "Uge {weekNumber}",
"N": "N", "N": "N",

View File

@ -33,5 +33,5 @@
"UPDATE_INFO_SINGLE": "Die aktuelle Installation ist {COMMIT_COUNT} Commit hinter dem {BRANCH_NAME} Branch.", "UPDATE_INFO_SINGLE": "Die aktuelle Installation ist {COMMIT_COUNT} Commit hinter dem {BRANCH_NAME} Branch.",
"UPDATE_INFO_MULTIPLE": "Die aktuelle Installation ist {COMMIT_COUNT} Commits hinter dem {BRANCH_NAME} Branch.", "UPDATE_INFO_MULTIPLE": "Die aktuelle Installation ist {COMMIT_COUNT} Commits hinter dem {BRANCH_NAME} Branch.",
"FEELS": "Gefühlt" "FEELS": "Gefühlt {DEGREE}"
} }

View File

@ -33,6 +33,6 @@
"UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.", "UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.",
"UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch.", "UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch.",
"FEELS": "Feels like", "FEELS": "Feels like {DEGREE}",
"PRECIP": "PoP" "PRECIP": "PoP"
} }

View File

@ -33,6 +33,6 @@
"UPDATE_INFO_SINGLE": "Tu actual instalación está {COMMIT_COUNT} commit cambios detrás de la rama {BRANCH_NAME}.", "UPDATE_INFO_SINGLE": "Tu actual instalación está {COMMIT_COUNT} commit cambios detrás de la rama {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Tu actual instalación está {COMMIT_COUNT} commits cambios detrás de la rama {BRANCH_NAME}.", "UPDATE_INFO_MULTIPLE": "Tu actual instalación está {COMMIT_COUNT} commits cambios detrás de la rama {BRANCH_NAME}.",
"FEELS": "Sensación térmica de", "FEELS": "Sensación térmica de {DEGREE}",
"PRECIP": "Precipitación" "PRECIP": "Precipitación"
} }

View File

@ -31,6 +31,6 @@
"UPDATE_INFO_SINGLE": "Nykyasennus on {COMMIT_COUNT} muutoksen jäljessä {BRANCH_NAME} haaraan nähden.", "UPDATE_INFO_SINGLE": "Nykyasennus on {COMMIT_COUNT} muutoksen jäljessä {BRANCH_NAME} haaraan nähden.",
"UPDATE_INFO_MULTIPLE": "Nykyasennus on {COMMIT_COUNT} muutosta jäljessä {BRANCH_NAME} haaraan nähden.", "UPDATE_INFO_MULTIPLE": "Nykyasennus on {COMMIT_COUNT} muutosta jäljessä {BRANCH_NAME} haaraan nähden.",
"FEELS": "Tuntuu kuin", "FEELS": "Tuntuu kuin {DEGREE}",
"PRECIP": "Sateen todennäköisyys" "PRECIP": "Sateen todennäköisyys"
} }

View File

@ -33,5 +33,5 @@
"UPDATE_INFO_SINGLE": "L'installation actuelle est {COMMIT_COUNT} commit en retard sur la branche {BRANCH_NAME} .", "UPDATE_INFO_SINGLE": "L'installation actuelle est {COMMIT_COUNT} commit en retard sur la branche {BRANCH_NAME} .",
"UPDATE_INFO_MULTIPLE": "L'installation actuelle est {COMMIT_COUNT} commits en retard sur la branche {BRANCH_NAME} .", "UPDATE_INFO_MULTIPLE": "L'installation actuelle est {COMMIT_COUNT} commits en retard sur la branche {BRANCH_NAME} .",
"FEELS": "Ressenti" "FEELS": "Ressenti {DEGREE}"
} }

View File

@ -31,6 +31,6 @@
"UPDATE_INFO_SINGLE": "ההתקנה הנוכחית נמצאת מאחור הענף {BRANCH_NAME} ב-{COMMIT_COUNT} מופע", "UPDATE_INFO_SINGLE": "ההתקנה הנוכחית נמצאת מאחור הענף {BRANCH_NAME} ב-{COMMIT_COUNT} מופע",
"UPDATE_INFO_MULTIPLE": "ההתקנה הנוכחית נמצאת מאחור הענף {BRANCH_NAME} ב-{COMMIT_COUNT} מופעים", "UPDATE_INFO_MULTIPLE": "ההתקנה הנוכחית נמצאת מאחור הענף {BRANCH_NAME} ב-{COMMIT_COUNT} מופעים",
"FEELS": "מרגיש כמו", "FEELS": "מרגיש כמו {DEGREE}",
"PRECIP": "משקעים" "PRECIP": "משקעים"
} }

View File

@ -14,13 +14,13 @@
"NE": "उपु", "NE": "उपु",
"ENE": "पुउपु", "ENE": "पुउपु",
"E": "पु", "E": "पु",
"ESE": "पुSपु", "ESE": "पुपु",
"SE": "दपु", "SE": "दपु",
"SSE": "ददपु", "SSE": "ददपु",
"S": "द", "S": "द",
"SSW": "ददW", "SSW": "दद",
"SW": "दW", "SW": "द",
"WSW": "WदW", "WSW": "पदप",
"W": "प", "W": "प",
"WNW": "पउप", "WNW": "पउप",
"NW": "उप", "NW": "उप",

View File

@ -31,5 +31,5 @@
"UPDATE_INFO_SINGLE": "Instalirana verzija {COMMIT_COUNT} commit kasni za branch-om {BRANCH_NAME}.", "UPDATE_INFO_SINGLE": "Instalirana verzija {COMMIT_COUNT} commit kasni za branch-om {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Instalirana verzija {COMMIT_COUNT} commit-ova kasni za branch-om {BRANCH_NAME}.", "UPDATE_INFO_MULTIPLE": "Instalirana verzija {COMMIT_COUNT} commit-ova kasni za branch-om {BRANCH_NAME}.",
"FEELS": "Osjećaj" "FEELS": "Osjećaj {DEGREE}"
} }

View File

@ -31,5 +31,5 @@
"UPDATE_INFO_SINGLE": "A jelenlegi telepítés óta {COMMIT_COUNT} új commit jelent meg a {BRANCH_NAME} ágon.", "UPDATE_INFO_SINGLE": "A jelenlegi telepítés óta {COMMIT_COUNT} új commit jelent meg a {BRANCH_NAME} ágon.",
"UPDATE_INFO_MULTIPLE": "A jelenlegi telepítés óta {COMMIT_COUNT} új commit jelent meg a {BRANCH_NAME} ágon.", "UPDATE_INFO_MULTIPLE": "A jelenlegi telepítés óta {COMMIT_COUNT} új commit jelent meg a {BRANCH_NAME} ágon.",
"FEELS": "Érzet" "FEELS": "Érzet {DEGREE}"
} }

View File

@ -31,5 +31,5 @@
"UPDATE_INFO_SINGLE": "L'installazione è {COMMIT_COUNT} commit indietro rispetto all'attuale branch {BRANCH_NAME}.", "UPDATE_INFO_SINGLE": "L'installazione è {COMMIT_COUNT} commit indietro rispetto all'attuale branch {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "L'installazione è {COMMIT_COUNT} commits indietro rispetto all'attuale branch {BRANCH_NAME}.", "UPDATE_INFO_MULTIPLE": "L'installazione è {COMMIT_COUNT} commits indietro rispetto all'attuale branch {BRANCH_NAME}.",
"FEELS": "Percepiti" "FEELS": "Percepiti {DEGREE}"
} }

View File

@ -31,6 +31,6 @@
"UPDATE_INFO_SINGLE": "Šis įdiegimas atsilieka {COMMIT_COUNT} įsipareigojimu {BRANCH_NAME} šakoje.", "UPDATE_INFO_SINGLE": "Šis įdiegimas atsilieka {COMMIT_COUNT} įsipareigojimu {BRANCH_NAME} šakoje.",
"UPDATE_INFO_MULTIPLE": "Šis įdiegimas atsilieka {COMMIT_COUNT} įsipareigojimais {BRANCH_NAME} šakoje.", "UPDATE_INFO_MULTIPLE": "Šis įdiegimas atsilieka {COMMIT_COUNT} įsipareigojimais {BRANCH_NAME} šakoje.",
"FEELS": "Jutiminė temp.", "FEELS": "Jutiminė temp. {DEGREE}",
"PRECIP": "Krituliai" "PRECIP": "Krituliai"
} }

View File

@ -31,5 +31,5 @@
"UPDATE_INFO_SINGLE": "Nåværende installasjon er {COMMIT_COUNT} commit bak {BRANCH_NAME} grenen.", "UPDATE_INFO_SINGLE": "Nåværende installasjon er {COMMIT_COUNT} commit bak {BRANCH_NAME} grenen.",
"UPDATE_INFO_MULTIPLE": "Nåværende installasjon er {COMMIT_COUNT} commits bak {BRANCH_NAME} grenen.", "UPDATE_INFO_MULTIPLE": "Nåværende installasjon er {COMMIT_COUNT} commits bak {BRANCH_NAME} grenen.",
"FEELS": "Føles som" "FEELS": "Føles som {DEGREE}"
} }

View File

@ -28,5 +28,5 @@
"UPDATE_NOTIFICATION_MODULE": "Update beschikbaar voor {MODULE_NAME} module.", "UPDATE_NOTIFICATION_MODULE": "Update beschikbaar voor {MODULE_NAME} module.",
"UPDATE_INFO_SINGLE": "De huidige installatie loopt {COMMIT_COUNT} commit achter op de {BRANCH_NAME} branch.", "UPDATE_INFO_SINGLE": "De huidige installatie loopt {COMMIT_COUNT} commit achter op de {BRANCH_NAME} branch.",
"UPDATE_INFO_MULTIPLE": "De huidige installatie loopt {COMMIT_COUNT} commits achter op de {BRANCH_NAME} branch.", "UPDATE_INFO_MULTIPLE": "De huidige installatie loopt {COMMIT_COUNT} commits achter op de {BRANCH_NAME} branch.",
"FEELS": "Voelt als" "FEELS": "Voelt als {DEGREE}"
} }

View File

@ -29,5 +29,5 @@
"UPDATE_INFO_SINGLE": "noverande installasjon er {COMMIT_COUNT} commit bak {BRANCH_NAME} greinen.", "UPDATE_INFO_SINGLE": "noverande installasjon er {COMMIT_COUNT} commit bak {BRANCH_NAME} greinen.",
"UPDATE_INFO_MULTIPLE": "noverande installasjon er {COMMIT_COUNT} commits bak {BRANCH_NAME} greinen.", "UPDATE_INFO_MULTIPLE": "noverande installasjon er {COMMIT_COUNT} commits bak {BRANCH_NAME} greinen.",
"FEELS": "Kjenst som" "FEELS": "Kjenst som {DEGREE}"
} }

View File

@ -31,6 +31,6 @@
"UPDATE_INFO_SINGLE": "Zainstalowana wersja odbiega o {COMMIT_COUNT} commit od gałęzi {BRANCH_NAME}.", "UPDATE_INFO_SINGLE": "Zainstalowana wersja odbiega o {COMMIT_COUNT} commit od gałęzi {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Zainstalowana wersja odbiega o {COMMIT_COUNT} commitów od gałęzi {BRANCH_NAME}.", "UPDATE_INFO_MULTIPLE": "Zainstalowana wersja odbiega o {COMMIT_COUNT} commitów od gałęzi {BRANCH_NAME}.",
"FEELS": "Odczuwalna", "FEELS": "Odczuwalna {DEGREE}",
"PRECIP": "Szansa opadów" "PRECIP": "Szansa opadów"
} }

View File

@ -33,5 +33,5 @@
"UPDATE_INFO_SINGLE": "اوسنی برخه {COMMIT_COUNT} د {BRANCH_NAME} څخه وروسته پاته ده", "UPDATE_INFO_SINGLE": "اوسنی برخه {COMMIT_COUNT} د {BRANCH_NAME} څخه وروسته پاته ده",
"UPDATE_INFO_MULTIPLE": "اوسنی برخه {COMMIT_COUNT} د {BRANCH_NAME} څخه وروسته پاته ده", "UPDATE_INFO_MULTIPLE": "اوسنی برخه {COMMIT_COUNT} د {BRANCH_NAME} څخه وروسته پاته ده",
"FEELS": "حس کېږی" "FEELS": "حس کېږی {DEGREE}"
} }

View File

@ -28,6 +28,6 @@
"UPDATE_INFO_SINGLE": "Sua versão atual é a {COMMIT_COUNT} commit dentro do seguinte branch {BRANCH_NAME}.", "UPDATE_INFO_SINGLE": "Sua versão atual é a {COMMIT_COUNT} commit dentro do seguinte branch {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Sua versão atual é a {COMMIT_COUNT} commits dentro do seguinte branch {BRANCH_NAME}.", "UPDATE_INFO_MULTIPLE": "Sua versão atual é a {COMMIT_COUNT} commits dentro do seguinte branch {BRANCH_NAME}.",
"FEELS": "Percebida", "FEELS": "Percebida {DEGREE}",
"PRECIP": "PoP" "PRECIP": "PoP"
} }

View File

@ -30,5 +30,5 @@
"UPDATE_INFO_SINGLE": "A instalação atual está {COMMIT_COUNT} commit atrasada no branch {BRANCH_NAME}.", "UPDATE_INFO_SINGLE": "A instalação atual está {COMMIT_COUNT} commit atrasada no branch {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "A instalação atual está {COMMIT_COUNT} commits atrasada no branch {BRANCH_NAME}.", "UPDATE_INFO_MULTIPLE": "A instalação atual está {COMMIT_COUNT} commits atrasada no branch {BRANCH_NAME}.",
"FEELS": "Sentida" "FEELS": "Sentida {DEGREE}"
} }

View File

@ -31,5 +31,5 @@
"UPDATE_INFO_SINGLE": "Există {COMMIT_COUNT} commit-uri noi pe branch-ul {BRANCH_NAME}.", "UPDATE_INFO_SINGLE": "Există {COMMIT_COUNT} commit-uri noi pe branch-ul {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Există {COMMIT_COUNT} commit-uri noi pe branch-ul {BRANCH_NAME}.", "UPDATE_INFO_MULTIPLE": "Există {COMMIT_COUNT} commit-uri noi pe branch-ul {BRANCH_NAME}.",
"FEELS": "Se simte ca fiind" "FEELS": "Se simte ca fiind {DEGREE}"
} }

View File

@ -25,7 +25,7 @@
"WNW": "ЗСЗ", "WNW": "ЗСЗ",
"NW": "СЗ", "NW": "СЗ",
"NNW": "ССЗ", "NNW": "ССЗ",
"FEELS": "По ощущению", "FEELS": "По ощущению {DEGREE}",
"UPDATE_NOTIFICATION": "Есть обновление для MagicMirror².", "UPDATE_NOTIFICATION": "Есть обновление для MagicMirror².",
"UPDATE_NOTIFICATION_MODULE": "Есть обновление для {MODULE_NAME} модуля.", "UPDATE_NOTIFICATION_MODULE": "Есть обновление для {MODULE_NAME} модуля.",

View File

@ -31,5 +31,5 @@
"UPDATE_INFO_SINGLE": "Denna installation ligger {COMMIT_COUNT} commit steg bakom {BRANCH_NAME} grenen.", "UPDATE_INFO_SINGLE": "Denna installation ligger {COMMIT_COUNT} commit steg bakom {BRANCH_NAME} grenen.",
"UPDATE_INFO_MULTIPLE": "Denna installation ligger {COMMIT_COUNT} commits steg bakom {BRANCH_NAME} grenen.", "UPDATE_INFO_MULTIPLE": "Denna installation ligger {COMMIT_COUNT} commits steg bakom {BRANCH_NAME} grenen.",
"FEELS": "Känns som" "FEELS": "Känns som {DEGREE}"
} }

View File

@ -31,5 +31,5 @@
"UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.", "UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.",
"UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch.", "UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch.",
"FEELS": "jem" "FEELS": "jem {DEGREE}"
} }

View File

@ -31,6 +31,6 @@
"UPDATE_INFO_SINGLE": "Sahip olduğunuz kurulum {BRANCH_NAME} branchinden {COMMIT_COUNT} commit geridedir.", "UPDATE_INFO_SINGLE": "Sahip olduğunuz kurulum {BRANCH_NAME} branchinden {COMMIT_COUNT} commit geridedir.",
"UPDATE_INFO_MULTIPLE": "Sahip olduğunuz kurulum {BRANCH_NAME} branchinden {COMMIT_COUNT} commit geridedir.", "UPDATE_INFO_MULTIPLE": "Sahip olduğunuz kurulum {BRANCH_NAME} branchinden {COMMIT_COUNT} commit geridedir.",
"FEELS": "Hissedilen", "FEELS": "Hissedilen {DEGREE}",
"PRECIP": "Yağış" "PRECIP": "Yağış"
} }

View File

@ -31,6 +31,6 @@
"UPDATE_INFO_SINGLE": "Поточна версія на {COMMIT_COUNT} комміт позаду від гілки {BRANCH_NAME}.", "UPDATE_INFO_SINGLE": "Поточна версія на {COMMIT_COUNT} комміт позаду від гілки {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "Поточна інсталяція на {COMMIT_COUNT} комітів позаду від гілки {BRANCH_NAME}.", "UPDATE_INFO_MULTIPLE": "Поточна інсталяція на {COMMIT_COUNT} комітів позаду від гілки {BRANCH_NAME}.",
"FEELS": "Відчувається як", "FEELS": "Відчувається як {DEGREE}",
"PRECIP": "Опади" "PRECIP": "Опади"
} }

View File

@ -31,5 +31,5 @@
"UPDATE_INFO_SINGLE": "当前已安装版本比{BRANCH_NAME}分支落后{COMMIT_COUNT}次代码更新。", "UPDATE_INFO_SINGLE": "当前已安装版本比{BRANCH_NAME}分支落后{COMMIT_COUNT}次代码更新。",
"UPDATE_INFO_MULTIPLE": "当前已安装版本比{BRANCH_NAME}分支落后{COMMIT_COUNT}次代码更新。", "UPDATE_INFO_MULTIPLE": "当前已安装版本比{BRANCH_NAME}分支落后{COMMIT_COUNT}次代码更新。",
"FEELS": "体感" "FEELS": "体感 {DEGREE}"
} }