Merge branch 'develop' into develop
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
"extends": ["eslint:recommended", "plugin:prettier/recommended", "plugin:jsdoc/recommended"],
|
||||||
"plugins": ["prettier"],
|
"plugins": ["prettier", "jsdoc"],
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
|
6
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
# Various Node ignoramuses.
|
# Various Node ignoramuses.
|
||||||
|
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
@ -13,9 +12,11 @@ build/Release
|
|||||||
/node_modules/**/*
|
/node_modules/**/*
|
||||||
fonts/node_modules/**/*
|
fonts/node_modules/**/*
|
||||||
vendor/node_modules/**/*
|
vendor/node_modules/**/*
|
||||||
|
!/tests/node_modules/**/*
|
||||||
jspm_modules
|
jspm_modules
|
||||||
.npm
|
.npm
|
||||||
.node_repl_history
|
.node_repl_history
|
||||||
|
.nyc_output/
|
||||||
|
|
||||||
# Visual Studio Code ignoramuses.
|
# Visual Studio Code ignoramuses.
|
||||||
.vscode/
|
.vscode/
|
||||||
@ -53,7 +54,6 @@ Temporary Items
|
|||||||
.apdisk
|
.apdisk
|
||||||
|
|
||||||
# Various Linux ignoramuses.
|
# Various Linux ignoramuses.
|
||||||
|
|
||||||
.fuse_hidden*
|
.fuse_hidden*
|
||||||
.directory
|
.directory
|
||||||
.Trash-*
|
.Trash-*
|
||||||
@ -76,5 +76,3 @@ Temporary Items
|
|||||||
*.orig
|
*.orig
|
||||||
*.rej
|
*.rej
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
!/tests/node_modules/**/*
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
package-lock.json
|
package-lock.json
|
||||||
/config/**/*
|
/config/**/*
|
||||||
/modules/default/calendar/vendor/ical.js/**/*
|
|
||||||
/vendor/**/*
|
/vendor/**/*
|
||||||
!/vendor/vendor.js
|
!/vendor/vendor.js
|
||||||
|
33
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
|
- 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
|
## [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.
|
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)
|
- Throw error when check_config fails. [#1928](https://github.com/MichMich/MagicMirror/issues/1928)
|
||||||
- Bug fix related to 'maxEntries' not displaying Calendar events. [#2050](https://github.com/MichMich/MagicMirror/issues/2050)
|
- Bug fix related to 'maxEntries' not displaying Calendar events. [#2050](https://github.com/MichMich/MagicMirror/issues/2050)
|
||||||
- Updated ical library to latest version. [#1926](https://github.com/MichMich/MagicMirror/issues/1926)
|
- Updated ical library to latest version. [#1926](https://github.com/MichMich/MagicMirror/issues/1926)
|
||||||
|
- Fix config check after merge of prettier [#2109](https://github.com/MichMich/MagicMirror/issues/2109)
|
||||||
|
|
||||||
## [2.11.0] - 2020-04-01
|
## [2.11.0] - 2020-04-01
|
||||||
|
|
||||||
|
@ -4,10 +4,19 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var config = {};
|
var config = {};
|
||||||
|
|
||||||
// Helper function to get server address/hostname from either the commandline or env
|
/**
|
||||||
|
* Helper function to get server address/hostname from either the commandline or env
|
||||||
|
*/
|
||||||
function getServerAddress() {
|
function getServerAddress() {
|
||||||
// Helper function to get command line parameters
|
/**
|
||||||
// Assumes that a cmdline parameter is defined with `--key [value]`
|
* Get command line parameters
|
||||||
|
* Assumes that a cmdline parameter is defined with `--key [value]`
|
||||||
|
*
|
||||||
|
* @param {string} key key to look for at the command line
|
||||||
|
* @param {string} defaultValue value if no key is given at the command line
|
||||||
|
*
|
||||||
|
* @returns {string} the value of the parameter
|
||||||
|
*/
|
||||||
function getCommandLineParameter(key, defaultValue = undefined) {
|
function getCommandLineParameter(key, defaultValue = undefined) {
|
||||||
var index = process.argv.indexOf(`--${key}`);
|
var index = process.argv.indexOf(`--${key}`);
|
||||||
var value = index > -1 ? process.argv[index + 1] : undefined;
|
var value = index > -1 ? process.argv[index + 1] : undefined;
|
||||||
@ -23,10 +32,17 @@
|
|||||||
config["tls"] = process.argv.indexOf("--use-tls") > 0;
|
config["tls"] = process.argv.indexOf("--use-tls") > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the config from the specified server url
|
||||||
|
*
|
||||||
|
* @param {string} url location where the server is running.
|
||||||
|
*
|
||||||
|
* @returns {Promise} the config
|
||||||
|
*/
|
||||||
function getServerConfig(url) {
|
function getServerConfig(url) {
|
||||||
// Return new pending promise
|
// Return new pending promise
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Select http or https module, depending on reqested url
|
// Select http or https module, depending on requested url
|
||||||
const lib = url.startsWith("https") ? require("https") : require("http");
|
const lib = url.startsWith("https") ? require("https") : require("http");
|
||||||
const request = lib.get(url, (response) => {
|
const request = lib.get(url, (response) => {
|
||||||
var configData = "";
|
var configData = "";
|
||||||
@ -47,6 +63,12 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a message to the console in case of errors
|
||||||
|
*
|
||||||
|
* @param {string} [message] error message to print
|
||||||
|
* @param {number} code error code for the exit call
|
||||||
|
*/
|
||||||
function fail(message, code = 1) {
|
function fail(message, code = 1) {
|
||||||
if (message !== undefined && typeof message === "string") {
|
if (message !== undefined && typeof message === "string") {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
|
65
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");
|
Log.error("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues");
|
||||||
});
|
});
|
||||||
|
|
||||||
/* App - The core app.
|
/**
|
||||||
|
* The core app.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
*/
|
*/
|
||||||
var App = function () {
|
var App = function () {
|
||||||
var nodeHelpers = [];
|
var nodeHelpers = [];
|
||||||
|
|
||||||
/* loadConfig(callback)
|
/**
|
||||||
* Loads the config file. combines it with the defaults,
|
* Loads the config file. Combines it with the defaults, and runs the
|
||||||
* and runs the callback with the found config as argument.
|
* callback with the found config as argument.
|
||||||
*
|
*
|
||||||
* argument callback function - The callback function.
|
* @param {Function} callback Function to be called after loading the config
|
||||||
*/
|
*/
|
||||||
var loadConfig = function (callback) {
|
var loadConfig = function (callback) {
|
||||||
Log.log("Loading config ...");
|
Log.log("Loading config ...");
|
||||||
@ -80,6 +83,12 @@ var App = function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the config for deprecated options and throws a warning in the logs
|
||||||
|
* if it encounters one option from the deprecated.js list
|
||||||
|
*
|
||||||
|
* @param {object} userConfig The user config
|
||||||
|
*/
|
||||||
var checkDeprecatedOptions = function (userConfig) {
|
var checkDeprecatedOptions = function (userConfig) {
|
||||||
var deprecated = require(global.root_path + "/js/deprecated.js");
|
var deprecated = require(global.root_path + "/js/deprecated.js");
|
||||||
var deprecatedOptions = deprecated.configs;
|
var deprecatedOptions = deprecated.configs;
|
||||||
@ -96,10 +105,11 @@ var App = function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* loadModule(module)
|
/**
|
||||||
* Loads a specific module.
|
* Loads a specific module.
|
||||||
*
|
*
|
||||||
* argument module string - The name of the module (including subpath).
|
* @param {string} module The name of the module (including subpath).
|
||||||
|
* @param {Function} callback Function to be called after loading
|
||||||
*/
|
*/
|
||||||
var loadModule = function (module, callback) {
|
var loadModule = function (module, callback) {
|
||||||
var elements = module.split("/");
|
var elements = module.split("/");
|
||||||
@ -144,10 +154,11 @@ var App = function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* loadModules(modules)
|
/**
|
||||||
* Loads all modules.
|
* Loads all modules.
|
||||||
*
|
*
|
||||||
* argument module string - The name of the module (including subpath).
|
* @param {Module[]} modules All modules to be loaded
|
||||||
|
* @param {Function} callback Function to be called after loading
|
||||||
*/
|
*/
|
||||||
var loadModules = function (modules, callback) {
|
var loadModules = function (modules, callback) {
|
||||||
Log.log("Loading module helpers ...");
|
Log.log("Loading module helpers ...");
|
||||||
@ -169,11 +180,14 @@ var App = function () {
|
|||||||
loadNextModule();
|
loadNextModule();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* cmpVersions(a,b)
|
/**
|
||||||
* Compare two semantic version numbers and return the difference.
|
* Compare two semantic version numbers and return the difference.
|
||||||
*
|
*
|
||||||
* argument a string - Version number a.
|
* @param {string} a Version number a.
|
||||||
* argument a string - Version number b.
|
* @param {string} b Version number b.
|
||||||
|
*
|
||||||
|
* @returns {number} A positive number if a is larger than b, a negative
|
||||||
|
* number if a is smaller and 0 if they are the same
|
||||||
*/
|
*/
|
||||||
function cmpVersions(a, b) {
|
function cmpVersions(a, b) {
|
||||||
var i, diff;
|
var i, diff;
|
||||||
@ -191,17 +205,20 @@ var App = function () {
|
|||||||
return segmentsA.length - segmentsB.length;
|
return segmentsA.length - segmentsB.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* start(callback)
|
/**
|
||||||
* This methods starts the core app.
|
* Start the core app.
|
||||||
* It loads the config, then it loads all modules.
|
|
||||||
* When it's done it executes the callback with the config as argument.
|
|
||||||
*
|
*
|
||||||
* argument callback function - The callback function.
|
* It loads the config, then it loads all modules. When it's done it
|
||||||
|
* executes the callback with the config as argument.
|
||||||
|
*
|
||||||
|
* @param {Function} callback Function to be called after start
|
||||||
*/
|
*/
|
||||||
this.start = function (callback) {
|
this.start = function (callback) {
|
||||||
loadConfig(function (c) {
|
loadConfig(function (c) {
|
||||||
config = c;
|
config = c;
|
||||||
|
|
||||||
|
Log.setLogLevel(config.logLevel);
|
||||||
|
|
||||||
var modules = [];
|
var modules = [];
|
||||||
|
|
||||||
for (var m in config.modules) {
|
for (var m in config.modules) {
|
||||||
@ -232,9 +249,10 @@ var App = function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* stop()
|
/**
|
||||||
* This methods stops the core app.
|
* Stops the core app. This calls each node_helper's STOP() function, if it
|
||||||
* This calls each node_helper's STOP() function, if it exists.
|
* exists.
|
||||||
|
*
|
||||||
* Added to fix #1056
|
* Added to fix #1056
|
||||||
*/
|
*/
|
||||||
this.stop = function () {
|
this.stop = function () {
|
||||||
@ -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
|
* Added to fix #1056
|
||||||
* Note: this is only used if running `server-only`. Otherwise
|
* Note: this is only used if running `server-only`. Otherwise
|
||||||
@ -261,7 +280,9 @@ var App = function () {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* We also need to listen to SIGTERM signals so we stop everything when we are asked to stop by the OS.
|
/**
|
||||||
|
* Listen to SIGTERM signals so we can stop everything when we
|
||||||
|
* are asked to stop by the OS.
|
||||||
*/
|
*/
|
||||||
process.on("SIGTERM", () => {
|
process.on("SIGTERM", () => {
|
||||||
Log.log("[SIGTERM] Received. Shutting down server...");
|
Log.log("[SIGTERM] Received. Shutting down server...");
|
||||||
|
@ -12,13 +12,14 @@ const path = require("path");
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
const rootPath = path.resolve(__dirname + "/../");
|
const rootPath = path.resolve(__dirname + "/../");
|
||||||
const config = require(rootPath + "/.eslintrc.json");
|
|
||||||
const Log = require(rootPath + "/js/logger.js");
|
const Log = require(rootPath + "/js/logger.js");
|
||||||
const Utils = require(rootPath + "/js/utils.js");
|
const Utils = require(rootPath + "/js/utils.js");
|
||||||
|
|
||||||
/* getConfigFile()
|
/**
|
||||||
* Return string with path of configuration file
|
* Returns a string with path of configuration file.
|
||||||
* Check if set by environment variable MM_CONFIG_FILE
|
* Check if set by environment variable MM_CONFIG_FILE
|
||||||
|
*
|
||||||
|
* @returns {string} path and filename of the config file
|
||||||
*/
|
*/
|
||||||
function getConfigFile() {
|
function getConfigFile() {
|
||||||
// FIXME: This function should be in core. Do you want refactor me ;) ?, be good!
|
// FIXME: This function should be in core. Do you want refactor me ;) ?, be good!
|
||||||
@ -29,6 +30,9 @@ function getConfigFile() {
|
|||||||
return configFileName;
|
return configFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the config file using eslint.
|
||||||
|
*/
|
||||||
function checkConfigFile() {
|
function checkConfigFile() {
|
||||||
const configFileName = getConfigFile();
|
const configFileName = getConfigFile();
|
||||||
|
|
||||||
@ -38,7 +42,7 @@ function checkConfigFile() {
|
|||||||
throw new Error("No config file present!");
|
throw new Error("No config file present!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check permission
|
// Check permission
|
||||||
try {
|
try {
|
||||||
fs.accessSync(configFileName, fs.F_OK);
|
fs.accessSync(configFileName, fs.F_OK);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -54,16 +58,15 @@ function checkConfigFile() {
|
|||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
const messages = linter.verify(data, config);
|
const messages = linter.verify(data);
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
Log.log("Your configuration file doesn't contain syntax errors :)");
|
Log.info(Utils.colors.pass("Your configuration file doesn't contain syntax errors :)"));
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
|
Log.error(Utils.colors.error("Your configuration file contains syntax errors :("));
|
||||||
// In case the there errors show messages and return
|
// In case the there errors show messages and return
|
||||||
messages.forEach((error) => {
|
messages.forEach((error) => {
|
||||||
Log.log("Line", error.line, "col", error.column, error.message);
|
Log.error("Line", error.line, "col", error.column, error.message);
|
||||||
});
|
});
|
||||||
throw new Error("Wrong syntax in config file!");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
13
js/class.js
@ -57,7 +57,9 @@
|
|||||||
: prop[name];
|
: prop[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
// The dummy class constructor
|
/**
|
||||||
|
* The dummy class constructor
|
||||||
|
*/
|
||||||
function Class() {
|
function Class() {
|
||||||
// All construction is actually done in the init method
|
// All construction is actually done in the init method
|
||||||
if (!initializing && this.init) {
|
if (!initializing && this.init) {
|
||||||
@ -78,8 +80,13 @@
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
//Define the clone method for later use.
|
/**
|
||||||
//Helper Method
|
* Define the clone method for later use. Helper Method.
|
||||||
|
*
|
||||||
|
* @param {object} obj Object to be cloned
|
||||||
|
*
|
||||||
|
* @returns {object} the cloned object
|
||||||
|
*/
|
||||||
function cloneObject(obj) {
|
function cloneObject(obj) {
|
||||||
if (obj === null || typeof obj !== "object") {
|
if (obj === null || typeof obj !== "object") {
|
||||||
return obj;
|
return obj;
|
||||||
|
@ -15,6 +15,9 @@ const BrowserWindow = electron.BrowserWindow;
|
|||||||
// be closed automatically when the JavaScript object is garbage collected.
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
||||||
var electronOptionsDefaults = {
|
var electronOptionsDefaults = {
|
||||||
|
44
js/loader.js
@ -15,7 +15,7 @@ var Loader = (function () {
|
|||||||
|
|
||||||
/* Private Methods */
|
/* Private Methods */
|
||||||
|
|
||||||
/* loadModules()
|
/**
|
||||||
* Loops thru all modules and requests load for every module.
|
* Loops thru all modules and requests load for every module.
|
||||||
*/
|
*/
|
||||||
var loadModules = function () {
|
var loadModules = function () {
|
||||||
@ -43,7 +43,7 @@ var Loader = (function () {
|
|||||||
loadNextModule();
|
loadNextModule();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* startModules()
|
/**
|
||||||
* Loops thru all modules and requests start for every module.
|
* Loops thru all modules and requests start for every module.
|
||||||
*/
|
*/
|
||||||
var startModules = function () {
|
var startModules = function () {
|
||||||
@ -56,19 +56,19 @@ var Loader = (function () {
|
|||||||
MM.modulesStarted(moduleObjects);
|
MM.modulesStarted(moduleObjects);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* getAllModules()
|
/**
|
||||||
* Retrieve list of all modules.
|
* Retrieve list of all modules.
|
||||||
*
|
*
|
||||||
* return array - module data as configured in config
|
* @returns {object[]} module data as configured in config
|
||||||
*/
|
*/
|
||||||
var getAllModules = function () {
|
var getAllModules = function () {
|
||||||
return config.modules;
|
return config.modules;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* getModuleData()
|
/**
|
||||||
* Generate array with module information including module paths.
|
* Generate array with module information including module paths.
|
||||||
*
|
*
|
||||||
* return array - Module information.
|
* @returns {object[]} Module information.
|
||||||
*/
|
*/
|
||||||
var getModuleData = function () {
|
var getModuleData = function () {
|
||||||
var modules = getAllModules();
|
var modules = getAllModules();
|
||||||
@ -106,11 +106,11 @@ var Loader = (function () {
|
|||||||
return moduleFiles;
|
return moduleFiles;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* loadModule(module)
|
/**
|
||||||
* Load modules via ajax request and create module objects.
|
* Load modules via ajax request and create module objects.s
|
||||||
*
|
*
|
||||||
* argument callback function - Function called when done.
|
* @param {object} module Information about the module we want to load.
|
||||||
* argument module object - Information about the module we want to load.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
var loadModule = function (module, callback) {
|
var loadModule = function (module, callback) {
|
||||||
var url = module.path + "/" + module.file;
|
var url = module.path + "/" + module.file;
|
||||||
@ -136,12 +136,12 @@ var Loader = (function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* bootstrapModule(module, mObj)
|
/**
|
||||||
* Bootstrap modules by setting the module data and loading the scripts & styles.
|
* Bootstrap modules by setting the module data and loading the scripts & styles.
|
||||||
*
|
*
|
||||||
* argument module object - Information about the module we want to load.
|
* @param {object} module Information about the module we want to load.
|
||||||
* argument mObj object - Modules instance.
|
* @param {Module} mObj Modules instance.
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
var bootstrapModule = function (module, mObj, callback) {
|
var bootstrapModule = function (module, mObj, callback) {
|
||||||
Log.info("Bootstrapping module: " + module.name);
|
Log.info("Bootstrapping module: " + module.name);
|
||||||
@ -161,11 +161,11 @@ var Loader = (function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* loadFile(fileName)
|
/**
|
||||||
* Load a script or stylesheet by adding it to the dom.
|
* Load a script or stylesheet by adding it to the dom.
|
||||||
*
|
*
|
||||||
* argument fileName string - Path of the file we want to load.
|
* @param {string} fileName Path of the file we want to load.
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
var loadFile = function (fileName, callback) {
|
var loadFile = function (fileName, callback) {
|
||||||
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
|
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
|
||||||
@ -215,20 +215,20 @@ var Loader = (function () {
|
|||||||
|
|
||||||
/* Public Methods */
|
/* Public Methods */
|
||||||
return {
|
return {
|
||||||
/* loadModules()
|
/**
|
||||||
* Load all modules as defined in the config.
|
* Load all modules as defined in the config.
|
||||||
*/
|
*/
|
||||||
loadModules: function () {
|
loadModules: function () {
|
||||||
loadModules();
|
loadModules();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* loadFile()
|
/**
|
||||||
* Load a file (script or stylesheet).
|
* Load a file (script or stylesheet).
|
||||||
* Prevent double loading and search for files in the vendor folder.
|
* Prevent double loading and search for files in the vendor folder.
|
||||||
*
|
*
|
||||||
* argument fileName string - Path of the file we want to load.
|
* @param {string} fileName Path of the file we want to load.
|
||||||
* argument module Module Object - the module that calls the loadFile function.
|
* @param {Module} module The module that calls the loadFile function.
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
loadFile: function (fileName, module, callback) {
|
loadFile: function (fileName, module, callback) {
|
||||||
if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) {
|
if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) {
|
||||||
|
18
js/logger.js
@ -19,7 +19,7 @@
|
|||||||
root.Log = factory(root.config);
|
root.Log = factory(root.config);
|
||||||
}
|
}
|
||||||
})(this, function (config) {
|
})(this, function (config) {
|
||||||
let logLevel = {
|
const logLevel = {
|
||||||
info: Function.prototype.bind.call(console.info, console),
|
info: Function.prototype.bind.call(console.info, console),
|
||||||
log: Function.prototype.bind.call(console.log, console),
|
log: Function.prototype.bind.call(console.log, console),
|
||||||
error: Function.prototype.bind.call(console.error, console),
|
error: Function.prototype.bind.call(console.error, console),
|
||||||
@ -32,13 +32,15 @@
|
|||||||
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
|
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config && config.logLevel) {
|
logLevel.setLogLevel = function (newLevel) {
|
||||||
Object.keys(logLevel).forEach(function (key, index) {
|
if (newLevel) {
|
||||||
if (!config.logLevel.includes(key.toLocaleUpperCase())) {
|
Object.keys(logLevel).forEach(function (key, index) {
|
||||||
logLevel[key] = function () {};
|
if (!newLevel.includes(key.toLocaleUpperCase())) {
|
||||||
}
|
logLevel[key] = function () {};
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return logLevel;
|
return logLevel;
|
||||||
});
|
});
|
||||||
|
178
js/main.js
@ -11,9 +11,8 @@ var MM = (function () {
|
|||||||
|
|
||||||
/* Private Methods */
|
/* Private Methods */
|
||||||
|
|
||||||
/* createDomObjects()
|
/**
|
||||||
* Create dom objects for all modules that
|
* Create dom objects for all modules that are configured for a specific position.
|
||||||
* are configured for a specific position.
|
|
||||||
*/
|
*/
|
||||||
var createDomObjects = function () {
|
var createDomObjects = function () {
|
||||||
var domCreationPromises = [];
|
var domCreationPromises = [];
|
||||||
@ -42,7 +41,7 @@ var MM = (function () {
|
|||||||
dom.appendChild(moduleHeader);
|
dom.appendChild(moduleHeader);
|
||||||
|
|
||||||
if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") {
|
if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") {
|
||||||
moduleHeader.style = "display: none;";
|
moduleHeader.style.display = "none;";
|
||||||
}
|
}
|
||||||
|
|
||||||
var moduleContent = document.createElement("div");
|
var moduleContent = document.createElement("div");
|
||||||
@ -65,10 +64,12 @@ var MM = (function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* selectWrapper(position)
|
/**
|
||||||
* Select the wrapper dom object for a specific position.
|
* Select the wrapper dom object for a specific position.
|
||||||
*
|
*
|
||||||
* argument position string - The name of the position.
|
* @param {string} position The name of the position.
|
||||||
|
*
|
||||||
|
* @returns {HTMLElement} the wrapper element
|
||||||
*/
|
*/
|
||||||
var selectWrapper = function (position) {
|
var selectWrapper = function (position) {
|
||||||
var classes = position.replace("_", " ");
|
var classes = position.replace("_", " ");
|
||||||
@ -81,13 +82,13 @@ var MM = (function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* sendNotification(notification, payload, sender)
|
/**
|
||||||
* Send a notification to all modules.
|
* Send a notification to all modules.
|
||||||
*
|
*
|
||||||
* argument notification string - The identifier of the notification.
|
* @param {string} notification The identifier of the notification.
|
||||||
* argument payload mixed - The payload of the notification.
|
* @param {*} payload The payload of the notification.
|
||||||
* argument sender Module - The module that sent the notification.
|
* @param {Module} sender The module that sent the notification.
|
||||||
* argument sendTo Module - The module to send the notification to. (optional)
|
* @param {Module} [sendTo] The (optional) module to send the notification to.
|
||||||
*/
|
*/
|
||||||
var sendNotification = function (notification, payload, sender, sendTo) {
|
var sendNotification = function (notification, payload, sender, sendTo) {
|
||||||
for (var m in modules) {
|
for (var m in modules) {
|
||||||
@ -98,13 +99,13 @@ var MM = (function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* updateDom(module, speed)
|
/**
|
||||||
* Update the dom for a specific module.
|
* Update the dom for a specific module.
|
||||||
*
|
*
|
||||||
* argument module Module - The module that needs an update.
|
* @param {Module} module The module that needs an update.
|
||||||
* argument speed Number - The number of microseconds for the animation. (optional)
|
* @param {number} [speed] The (optional) number of microseconds for the animation.
|
||||||
*
|
*
|
||||||
* return Promise - Resolved when the dom is fully updated.
|
* @returns {Promise} Resolved when the dom is fully updated.
|
||||||
*/
|
*/
|
||||||
var updateDom = function (module, speed) {
|
var updateDom = function (module, speed) {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
@ -126,15 +127,15 @@ var MM = (function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* updateDomWithContent(module, speed, newHeader, newContent)
|
/**
|
||||||
* Update the dom with the specified content
|
* Update the dom with the specified content
|
||||||
*
|
*
|
||||||
* argument module Module - The module that needs an update.
|
* @param {Module} module The module that needs an update.
|
||||||
* argument speed Number - The number of microseconds for the animation. (optional)
|
* @param {number} [speed] The (optional) number of microseconds for the animation.
|
||||||
* argument newHeader String - The new header that is generated.
|
* @param {string} newHeader The new header that is generated.
|
||||||
* argument newContent Domobject - The new content that is generated.
|
* @param {HTMLElement} newContent The new content that is generated.
|
||||||
*
|
*
|
||||||
* return Promise - Resolved when the module dom has been updated.
|
* @returns {Promise} Resolved when the module dom has been updated.
|
||||||
*/
|
*/
|
||||||
var updateDomWithContent = function (module, speed, newHeader, newContent) {
|
var updateDomWithContent = function (module, speed, newHeader, newContent) {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
@ -165,14 +166,14 @@ var MM = (function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* moduleNeedsUpdate(module, newContent)
|
/**
|
||||||
* Check if the content has changed.
|
* Check if the content has changed.
|
||||||
*
|
*
|
||||||
* argument module Module - The module to check.
|
* @param {Module} module The module to check.
|
||||||
* argument newHeader String - The new header that is generated.
|
* @param {string} newHeader The new header that is generated.
|
||||||
* argument newContent Domobject - The new content that is generated.
|
* @param {HTMLElement} newContent The new content that is generated.
|
||||||
*
|
*
|
||||||
* return bool - Does the module need an update?
|
* @returns {boolean} True if the module need an update, false otherwise
|
||||||
*/
|
*/
|
||||||
var moduleNeedsUpdate = function (module, newHeader, newContent) {
|
var moduleNeedsUpdate = function (module, newHeader, newContent) {
|
||||||
var moduleWrapper = document.getElementById(module.identifier);
|
var moduleWrapper = document.getElementById(module.identifier);
|
||||||
@ -197,12 +198,12 @@ var MM = (function () {
|
|||||||
return headerNeedsUpdate || contentNeedsUpdate;
|
return headerNeedsUpdate || contentNeedsUpdate;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* moduleNeedsUpdate(module, newContent)
|
/**
|
||||||
* Update the content of a module on screen.
|
* Update the content of a module on screen.
|
||||||
*
|
*
|
||||||
* argument module Module - The module to check.
|
* @param {Module} module The module to check.
|
||||||
* argument newHeader String - The new header that is generated.
|
* @param {string} newHeader The new header that is generated.
|
||||||
* argument newContent Domobject - The new content that is generated.
|
* @param {HTMLElement} newContent The new content that is generated.
|
||||||
*/
|
*/
|
||||||
var updateModuleContent = function (module, newHeader, newContent) {
|
var updateModuleContent = function (module, newHeader, newContent) {
|
||||||
var moduleWrapper = document.getElementById(module.identifier);
|
var moduleWrapper = document.getElementById(module.identifier);
|
||||||
@ -216,15 +217,20 @@ var MM = (function () {
|
|||||||
contentWrapper[0].appendChild(newContent);
|
contentWrapper[0].appendChild(newContent);
|
||||||
|
|
||||||
headerWrapper[0].innerHTML = newHeader;
|
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.
|
* Hide the module.
|
||||||
*
|
*
|
||||||
* argument module Module - The module to hide.
|
* @param {Module} module The module to hide.
|
||||||
* argument speed Number - The speed of the hide animation.
|
* @param {number} speed The speed of the hide animation.
|
||||||
* argument callback function - Called when the animation is done.
|
* @param {Function} callback Called when the animation is done.
|
||||||
|
* @param {object} [options] Optional settings for the hide method.
|
||||||
*/
|
*/
|
||||||
var hideModule = function (module, speed, callback, options) {
|
var hideModule = function (module, speed, callback, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
@ -264,12 +270,13 @@ var MM = (function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* showModule(module, speed, callback)
|
/**
|
||||||
* Show the module.
|
* Show the module.
|
||||||
*
|
*
|
||||||
* argument module Module - The module to show.
|
* @param {Module} module The module to show.
|
||||||
* argument speed Number - The speed of the show animation.
|
* @param {number} speed The speed of the show animation.
|
||||||
* argument callback function - Called when the animation is done.
|
* @param {Function} callback Called when the animation is done.
|
||||||
|
* @param {object} [options] Optional settings for the show method.
|
||||||
*/
|
*/
|
||||||
var showModule = function (module, speed, callback, options) {
|
var showModule = function (module, speed, callback, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
@ -323,7 +330,7 @@ var MM = (function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* updateWrapperStates()
|
/**
|
||||||
* Checks for all positions if it has visible content.
|
* Checks for all positions if it has visible content.
|
||||||
* If not, if will hide the position to prevent unwanted margins.
|
* If not, if will hide the position to prevent unwanted margins.
|
||||||
* This method should be called by the show and hide methods.
|
* This method should be called by the show and hide methods.
|
||||||
@ -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 () {
|
var loadConfig = function () {
|
||||||
// FIXME: Think about how to pass config around without breaking tests
|
// FIXME: Think about how to pass config around without breaking tests
|
||||||
@ -368,41 +375,41 @@ var MM = (function () {
|
|||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* setSelectionMethodsForModules()
|
/**
|
||||||
* Adds special selectors on a collection of modules.
|
* Adds special selectors on a collection of modules.
|
||||||
*
|
*
|
||||||
* argument modules array - Array of modules.
|
* @param {Module[]} modules Array of modules.
|
||||||
*/
|
*/
|
||||||
var setSelectionMethodsForModules = function (modules) {
|
var setSelectionMethodsForModules = function (modules) {
|
||||||
/* withClass(className)
|
/**
|
||||||
* calls modulesByClass to filter modules with the specified classes.
|
* Filter modules with the specified classes.
|
||||||
*
|
*
|
||||||
* argument className string/array - one or multiple classnames. (array or space divided)
|
* @param {string|string[]} className one or multiple classnames (array or space divided).
|
||||||
*
|
*
|
||||||
* return array - Filtered collection of modules.
|
* @returns {Module[]} Filtered collection of modules.
|
||||||
*/
|
*/
|
||||||
var withClass = function (className) {
|
var withClass = function (className) {
|
||||||
return modulesByClass(className, true);
|
return modulesByClass(className, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* exceptWithClass(className)
|
/**
|
||||||
* calls modulesByClass to filter modules without the specified classes.
|
* Filter modules without the specified classes.
|
||||||
*
|
*
|
||||||
* argument className string/array - one or multiple classnames. (array or space divided)
|
* @param {string|string[]} className one or multiple classnames (array or space divided).
|
||||||
*
|
*
|
||||||
* return array - Filtered collection of modules.
|
* @returns {Module[]} Filtered collection of modules.
|
||||||
*/
|
*/
|
||||||
var exceptWithClass = function (className) {
|
var exceptWithClass = function (className) {
|
||||||
return modulesByClass(className, false);
|
return modulesByClass(className, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* modulesByClass(className, include)
|
/**
|
||||||
* filters a collection of modules based on classname(s).
|
* Filters a collection of modules based on classname(s).
|
||||||
*
|
*
|
||||||
* argument className string/array - one or multiple classnames. (array or space divided)
|
* @param {string|string[]} className one or multiple classnames (array or space divided).
|
||||||
* argument include boolean - if the filter should include or exclude the modules with the specific classes.
|
* @param {boolean} include if the filter should include or exclude the modules with the specific classes.
|
||||||
*
|
*
|
||||||
* return array - Filtered collection of modules.
|
* @returns {Module[]} Filtered collection of modules.
|
||||||
*/
|
*/
|
||||||
var modulesByClass = function (className, include) {
|
var modulesByClass = function (className, include) {
|
||||||
var searchClasses = className;
|
var searchClasses = className;
|
||||||
@ -427,12 +434,12 @@ var MM = (function () {
|
|||||||
return newModules;
|
return newModules;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* exceptModule(module)
|
/**
|
||||||
* Removes a module instance from the collection.
|
* Removes a module instance from the collection.
|
||||||
*
|
*
|
||||||
* argument module Module object - The module instance to remove from the collection.
|
* @param {object} module The module instance to remove from the collection.
|
||||||
*
|
*
|
||||||
* return array - Filtered collection of modules.
|
* @returns {Module[]} Filtered collection of modules.
|
||||||
*/
|
*/
|
||||||
var exceptModule = function (module) {
|
var exceptModule = function (module) {
|
||||||
var newModules = modules.filter(function (mod) {
|
var newModules = modules.filter(function (mod) {
|
||||||
@ -443,10 +450,10 @@ var MM = (function () {
|
|||||||
return newModules;
|
return newModules;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* enumerate(callback)
|
/**
|
||||||
* Walks thru a collection of modules and executes the callback with the module as an argument.
|
* Walks thru a collection of modules and executes the callback with the module as an argument.
|
||||||
*
|
*
|
||||||
* argument callback function - The function to execute with the module as an argument.
|
* @param {Function} callback The function to execute with the module as an argument.
|
||||||
*/
|
*/
|
||||||
var enumerate = function (callback) {
|
var enumerate = function (callback) {
|
||||||
modules.map(function (module) {
|
modules.map(function (module) {
|
||||||
@ -471,20 +478,23 @@ var MM = (function () {
|
|||||||
return {
|
return {
|
||||||
/* Public Methods */
|
/* Public Methods */
|
||||||
|
|
||||||
/* init()
|
/**
|
||||||
* Main init method.
|
* Main init method.
|
||||||
*/
|
*/
|
||||||
init: function () {
|
init: function () {
|
||||||
Log.info("Initializing MagicMirror.");
|
Log.info("Initializing MagicMirror.");
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
|
||||||
|
Log.setLogLevel(config.logLevel);
|
||||||
|
|
||||||
Translator.loadCoreTranslations(config.language);
|
Translator.loadCoreTranslations(config.language);
|
||||||
Loader.loadModules();
|
Loader.loadModules();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* modulesStarted(moduleObjects)
|
/**
|
||||||
* Gets called when all modules are started.
|
* Gets called when all modules are started.
|
||||||
*
|
*
|
||||||
* argument moduleObjects array<Module> - All module instances.
|
* @param {Module[]} moduleObjects All module instances.
|
||||||
*/
|
*/
|
||||||
modulesStarted: function (moduleObjects) {
|
modulesStarted: function (moduleObjects) {
|
||||||
modules = [];
|
modules = [];
|
||||||
@ -499,12 +509,12 @@ var MM = (function () {
|
|||||||
createDomObjects();
|
createDomObjects();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* sendNotification(notification, payload, sender)
|
/**
|
||||||
* Send a notification to all modules.
|
* Send a notification to all modules.
|
||||||
*
|
*
|
||||||
* argument notification string - The identifier of the notification.
|
* @param {string} notification The identifier of the notification.
|
||||||
* argument payload mixed - The payload of the notification.
|
* @param {*} payload The payload of the notification.
|
||||||
* argument sender Module - The module that sent the notification.
|
* @param {Module} sender The module that sent the notification.
|
||||||
*/
|
*/
|
||||||
sendNotification: function (notification, payload, sender) {
|
sendNotification: function (notification, payload, sender) {
|
||||||
if (arguments.length < 3) {
|
if (arguments.length < 3) {
|
||||||
@ -526,11 +536,11 @@ var MM = (function () {
|
|||||||
sendNotification(notification, payload, sender);
|
sendNotification(notification, payload, sender);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* updateDom(module, speed)
|
/**
|
||||||
* Update the dom for a specific module.
|
* Update the dom for a specific module.
|
||||||
*
|
*
|
||||||
* argument module Module - The module that needs an update.
|
* @param {Module} module The module that needs an update.
|
||||||
* argument speed Number - The number of microseconds for the animation. (optional)
|
* @param {number} [speed] The number of microseconds for the animation.
|
||||||
*/
|
*/
|
||||||
updateDom: function (module, speed) {
|
updateDom: function (module, speed) {
|
||||||
if (!(module instanceof Module)) {
|
if (!(module instanceof Module)) {
|
||||||
@ -542,36 +552,36 @@ var MM = (function () {
|
|||||||
updateDom(module, speed);
|
updateDom(module, speed);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getModules(module, speed)
|
/**
|
||||||
* Returns a collection of all modules currently active.
|
* Returns a collection of all modules currently active.
|
||||||
*
|
*
|
||||||
* return array - A collection of all modules currently active.
|
* @returns {Module[]} A collection of all modules currently active.
|
||||||
*/
|
*/
|
||||||
getModules: function () {
|
getModules: function () {
|
||||||
setSelectionMethodsForModules(modules);
|
setSelectionMethodsForModules(modules);
|
||||||
return modules;
|
return modules;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* hideModule(module, speed, callback)
|
/**
|
||||||
* Hide the module.
|
* Hide the module.
|
||||||
*
|
*
|
||||||
* argument module Module - The module hide.
|
* @param {Module} module The module to hide.
|
||||||
* argument speed Number - The speed of the hide animation.
|
* @param {number} speed The speed of the hide animation.
|
||||||
* argument callback function - Called when the animation is done.
|
* @param {Function} callback Called when the animation is done.
|
||||||
* argument options object - Optional settings for the hide method.
|
* @param {object} [options] Optional settings for the hide method.
|
||||||
*/
|
*/
|
||||||
hideModule: function (module, speed, callback, options) {
|
hideModule: function (module, speed, callback, options) {
|
||||||
module.hidden = true;
|
module.hidden = true;
|
||||||
hideModule(module, speed, callback, options);
|
hideModule(module, speed, callback, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* showModule(module, speed, callback)
|
/**
|
||||||
* Show the module.
|
* Show the module.
|
||||||
*
|
*
|
||||||
* argument module Module - The module show.
|
* @param {Module} module The module to show.
|
||||||
* argument speed Number - The speed of the show animation.
|
* @param {number} speed The speed of the show animation.
|
||||||
* argument callback function - Called when the animation is done.
|
* @param {Function} callback Called when the animation is done.
|
||||||
* argument options object - Optional settings for the hide method.
|
* @param {object} [options] Optional settings for the show method.
|
||||||
*/
|
*/
|
||||||
showModule: function (module, speed, callback, options) {
|
showModule: function (module, speed, callback, options) {
|
||||||
// do not change module.hidden yet, only if we really show it later
|
// do not change module.hidden yet, only if we really show it later
|
||||||
|
200
js/module.js
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
/* Magic Mirror
|
/* Magic Mirror
|
||||||
* Module Blueprint.
|
* Module Blueprint.
|
||||||
|
* @typedef {Object} Module
|
||||||
*
|
*
|
||||||
* By Michael Teeuw https://michaelteeuw.nl
|
* By Michael Teeuw https://michaelteeuw.nl
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
var Module = Class.extend({
|
var Module = Class.extend({
|
||||||
/*********************************************************
|
/*********************************************************
|
||||||
@ -29,53 +31,55 @@ var Module = Class.extend({
|
|||||||
// Use the nunjucksEnvironment() to get it.
|
// Use the nunjucksEnvironment() to get it.
|
||||||
_nunjucksEnvironment: null,
|
_nunjucksEnvironment: null,
|
||||||
|
|
||||||
/* init()
|
/**
|
||||||
* Is called when the module is instantiated.
|
* Called when the module is instantiated.
|
||||||
*/
|
*/
|
||||||
init: function () {
|
init: function () {
|
||||||
//Log.log(this.defaults);
|
//Log.log(this.defaults);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* start()
|
/**
|
||||||
* Is called when the module is started.
|
* Called when the module is started.
|
||||||
*/
|
*/
|
||||||
start: function () {
|
start: function () {
|
||||||
Log.info("Starting module: " + this.name);
|
Log.info("Starting module: " + this.name);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getScripts()
|
/**
|
||||||
* Returns a list of scripts the module requires to be loaded.
|
* Returns a list of scripts the module requires to be loaded.
|
||||||
*
|
*
|
||||||
* return Array<String> - An array with filenames.
|
* @returns {string[]} An array with filenames.
|
||||||
*/
|
*/
|
||||||
getScripts: function () {
|
getScripts: function () {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getStyles()
|
/**
|
||||||
* Returns a list of stylesheets the module requires to be loaded.
|
* Returns a list of stylesheets the module requires to be loaded.
|
||||||
*
|
*
|
||||||
* return Array<String> - An array with filenames.
|
* @returns {string[]} An array with filenames.
|
||||||
*/
|
*/
|
||||||
getStyles: function () {
|
getStyles: function () {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getTranslations()
|
/**
|
||||||
* Returns a map of translation files the module requires to be loaded.
|
* Returns a map of translation files the module requires to be loaded.
|
||||||
*
|
*
|
||||||
* return Map<String, String> - A map with langKeys and filenames.
|
* return Map<String, String> -
|
||||||
|
*
|
||||||
|
* @returns {*} A map with langKeys and filenames.
|
||||||
*/
|
*/
|
||||||
getTranslations: function () {
|
getTranslations: function () {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getDom()
|
/**
|
||||||
* This method generates the dom which needs to be displayed. This method is called by the Magic Mirror core.
|
* Generates the dom which needs to be displayed. This method is called by the Magic Mirror core.
|
||||||
* This method can to be subclassed if the module wants to display info on the mirror.
|
* This method can to be subclassed if the module wants to display info on the mirror.
|
||||||
* Alternatively, the getTemplate method could be subclassed.
|
* Alternatively, the getTemplate method could be subclassed.
|
||||||
*
|
*
|
||||||
* return DomObject | Promise - The dom or a promise with the dom to display.
|
* @returns {HTMLElement|Promise} The dom or a promise with the dom to display.
|
||||||
*/
|
*/
|
||||||
getDom: function () {
|
getDom: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -105,46 +109,45 @@ var Module = Class.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getHeader()
|
/**
|
||||||
* This method generates the header string which needs to be displayed if a user has a header configured for this module.
|
* Generates the header string which needs to be displayed if a user has a header configured for this module.
|
||||||
* This method is called by the Magic Mirror core, but only if the user has configured a default header for the module.
|
* This method is called by the Magic Mirror core, but only if the user has configured a default header for the module.
|
||||||
* This method needs to be subclassed if the module wants to display modified headers on the mirror.
|
* This method needs to be subclassed if the module wants to display modified headers on the mirror.
|
||||||
*
|
*
|
||||||
* return string - The header to display above the header.
|
* @returns {string} The header to display above the header.
|
||||||
*/
|
*/
|
||||||
getHeader: function () {
|
getHeader: function () {
|
||||||
return this.data.header;
|
return this.data.header;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getTemplate()
|
/**
|
||||||
* This method returns the template for the module which is used by the default getDom implementation.
|
* Returns the template for the module which is used by the default getDom implementation.
|
||||||
* This method needs to be subclassed if the module wants to use a template.
|
* This method needs to be subclassed if the module wants to use a template.
|
||||||
* It can either return a template sting, or a template filename.
|
* It can either return a template sting, or a template filename.
|
||||||
* If the string ends with '.html' it's considered a file from within the module's folder.
|
* If the string ends with '.html' it's considered a file from within the module's folder.
|
||||||
*
|
*
|
||||||
* return string - The template string of filename.
|
* @returns {string} The template string of filename.
|
||||||
*/
|
*/
|
||||||
getTemplate: function () {
|
getTemplate: function () {
|
||||||
return '<div class="normal">' + this.name + '</div><div class="small dimmed">' + this.identifier + "</div>";
|
return '<div class="normal">' + this.name + '</div><div class="small dimmed">' + this.identifier + "</div>";
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getTemplateData()
|
/**
|
||||||
* This method returns the data to be used in the template.
|
* Returns the data to be used in the template.
|
||||||
* This method needs to be subclassed if the module wants to use a custom data.
|
* This method needs to be subclassed if the module wants to use a custom data.
|
||||||
*
|
*
|
||||||
* return Object
|
* @returns {object} The data for the template
|
||||||
*/
|
*/
|
||||||
getTemplateData: function () {
|
getTemplateData: function () {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
/* notificationReceived(notification, payload, sender)
|
/**
|
||||||
* This method is called when a notification arrives.
|
* Called by the Magic Mirror core when a notification arrives.
|
||||||
* This method is called by the Magic Mirror core.
|
|
||||||
*
|
*
|
||||||
* argument notification string - The identifier of the notification.
|
* @param {string} notification The identifier of the notification.
|
||||||
* argument payload mixed - The payload of the notification.
|
* @param {*} payload The payload of the notification.
|
||||||
* argument sender Module - The module that sent the notification.
|
* @param {Module} sender The module that sent the notification.
|
||||||
*/
|
*/
|
||||||
notificationReceived: function (notification, payload, sender) {
|
notificationReceived: function (notification, payload, sender) {
|
||||||
if (sender) {
|
if (sender) {
|
||||||
@ -154,11 +157,11 @@ var Module = Class.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** nunjucksEnvironment()
|
/**
|
||||||
* Returns the nunjucks environment for the current module.
|
* Returns the nunjucks environment for the current module.
|
||||||
* The environment is checked in the _nunjucksEnvironment instance variable.
|
* The environment is checked in the _nunjucksEnvironment instance variable.
|
||||||
|
*
|
||||||
* @returns Nunjucks Environment
|
* @returns {object} The Nunjucks Environment
|
||||||
*/
|
*/
|
||||||
nunjucksEnvironment: function () {
|
nunjucksEnvironment: function () {
|
||||||
if (this._nunjucksEnvironment !== null) {
|
if (this._nunjucksEnvironment !== null) {
|
||||||
@ -171,6 +174,7 @@ var Module = Class.extend({
|
|||||||
trimBlocks: true,
|
trimBlocks: true,
|
||||||
lstripBlocks: true
|
lstripBlocks: true
|
||||||
});
|
});
|
||||||
|
|
||||||
this._nunjucksEnvironment.addFilter("translate", function (str) {
|
this._nunjucksEnvironment.addFilter("translate", function (str) {
|
||||||
return self.translate(str);
|
return self.translate(str);
|
||||||
});
|
});
|
||||||
@ -178,25 +182,25 @@ var Module = Class.extend({
|
|||||||
return this._nunjucksEnvironment;
|
return this._nunjucksEnvironment;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* socketNotificationReceived(notification, payload)
|
/**
|
||||||
* This method is called when a socket notification arrives.
|
* Called when a socket notification arrives.
|
||||||
*
|
*
|
||||||
* argument notification string - The identifier of the notification.
|
* @param {string} notification The identifier of the notification.
|
||||||
* argument payload mixed - The payload of the notification.
|
* @param {*} payload The payload of the notification.
|
||||||
*/
|
*/
|
||||||
socketNotificationReceived: function (notification, payload) {
|
socketNotificationReceived: function (notification, payload) {
|
||||||
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
|
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* suspend()
|
/*
|
||||||
* This method is called when a module is hidden.
|
* Called when the module is hidden.
|
||||||
*/
|
*/
|
||||||
suspend: function () {
|
suspend: function () {
|
||||||
Log.log(this.name + " is suspended.");
|
Log.log(this.name + " is suspended.");
|
||||||
},
|
},
|
||||||
|
|
||||||
/* resume()
|
/*
|
||||||
* This method is called when a module is shown.
|
* Called when the module is shown.
|
||||||
*/
|
*/
|
||||||
resume: function () {
|
resume: function () {
|
||||||
Log.log(this.name + " is resumed.");
|
Log.log(this.name + " is resumed.");
|
||||||
@ -206,10 +210,10 @@ var Module = Class.extend({
|
|||||||
* The methods below don"t need subclassing. *
|
* The methods below don"t need subclassing. *
|
||||||
*********************************************/
|
*********************************************/
|
||||||
|
|
||||||
/* setData(data)
|
/**
|
||||||
* Set the module data.
|
* Set the module data.
|
||||||
*
|
*
|
||||||
* argument data object - Module data.
|
* @param {Module} data The module data
|
||||||
*/
|
*/
|
||||||
setData: function (data) {
|
setData: function (data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@ -220,18 +224,20 @@ var Module = Class.extend({
|
|||||||
this.setConfig(data.config);
|
this.setConfig(data.config);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* setConfig(config)
|
/**
|
||||||
* Set the module config and combine it with the module defaults.
|
* Set the module config and combine it with the module defaults.
|
||||||
*
|
*
|
||||||
* argument config object - Module config.
|
* @param {object} config The combined module config.
|
||||||
*/
|
*/
|
||||||
setConfig: function (config) {
|
setConfig: function (config) {
|
||||||
this.config = Object.assign({}, this.defaults, config);
|
this.config = Object.assign({}, this.defaults, config);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* socket()
|
/**
|
||||||
* Returns a socket object. If it doesn't exist, it"s created.
|
* Returns a socket object. If it doesn't exist, it's created.
|
||||||
* It also registers the notification callback.
|
* It also registers the notification callback.
|
||||||
|
*
|
||||||
|
* @returns {MMSocket} a socket object
|
||||||
*/
|
*/
|
||||||
socket: function () {
|
socket: function () {
|
||||||
if (typeof this._socket === "undefined") {
|
if (typeof this._socket === "undefined") {
|
||||||
@ -246,40 +252,39 @@ var Module = Class.extend({
|
|||||||
return this._socket;
|
return this._socket;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* file(file)
|
/**
|
||||||
* Retrieve the path to a module file.
|
* Retrieve the path to a module file.
|
||||||
*
|
*
|
||||||
* argument file string - Filename.
|
* @param {string} file Filename
|
||||||
*
|
* @returns {string} the file path
|
||||||
* return string - File path.
|
|
||||||
*/
|
*/
|
||||||
file: function (file) {
|
file: function (file) {
|
||||||
return (this.data.path + "/" + file).replace("//", "/");
|
return (this.data.path + "/" + file).replace("//", "/");
|
||||||
},
|
},
|
||||||
|
|
||||||
/* loadStyles()
|
/**
|
||||||
* Load all required stylesheets by requesting the MM object to load the files.
|
* Load all required stylesheets by requesting the MM object to load the files.
|
||||||
*
|
*
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
loadStyles: function (callback) {
|
loadStyles: function (callback) {
|
||||||
this.loadDependencies("getStyles", callback);
|
this.loadDependencies("getStyles", callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* loadScripts()
|
/**
|
||||||
* Load all required scripts by requesting the MM object to load the files.
|
* Load all required scripts by requesting the MM object to load the files.
|
||||||
*
|
*
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
loadScripts: function (callback) {
|
loadScripts: function (callback) {
|
||||||
this.loadDependencies("getScripts", callback);
|
this.loadDependencies("getScripts", callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* loadDependencies(funcName, callback)
|
/**
|
||||||
* Helper method to load all dependencies.
|
* Helper method to load all dependencies.
|
||||||
*
|
*
|
||||||
* argument funcName string - Function name to call to get scripts or styles.
|
* @param {string} funcName Function name to call to get scripts or styles.
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
loadDependencies: function (funcName, callback) {
|
loadDependencies: function (funcName, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -300,10 +305,10 @@ var Module = Class.extend({
|
|||||||
loadNextDependency();
|
loadNextDependency();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* loadScripts()
|
/**
|
||||||
* Load all required scripts by requesting the MM object to load the files.
|
* Load all translations.
|
||||||
*
|
*
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
loadTranslations: function (callback) {
|
loadTranslations: function (callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -334,12 +339,13 @@ var Module = Class.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/* translate(key, defaultValueOrVariables, defaultValue)
|
/**
|
||||||
* Request the translation for a given key with optional variables and default value.
|
* Request the translation for a given key with optional variables and default value.
|
||||||
*
|
*
|
||||||
* argument key string - The key of the string to translate
|
* @param {string} key The key of the string to translate
|
||||||
* argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional)
|
* @param {string|object} [defaultValueOrVariables] The default value or variables for translating.
|
||||||
* argument defaultValue string - The default value with variables. (Optional)
|
* @param {string} [defaultValue] The default value with variables.
|
||||||
|
* @returns {string} the translated key
|
||||||
*/
|
*/
|
||||||
translate: function (key, defaultValueOrVariables, defaultValue) {
|
translate: function (key, defaultValueOrVariables, defaultValue) {
|
||||||
if (typeof defaultValueOrVariables === "object") {
|
if (typeof defaultValueOrVariables === "object") {
|
||||||
@ -348,41 +354,41 @@ var Module = Class.extend({
|
|||||||
return Translator.translate(this, key) || defaultValueOrVariables || "";
|
return Translator.translate(this, key) || defaultValueOrVariables || "";
|
||||||
},
|
},
|
||||||
|
|
||||||
/* updateDom(speed)
|
/**
|
||||||
* Request an (animated) update of the module.
|
* Request an (animated) update of the module.
|
||||||
*
|
*
|
||||||
* argument speed Number - The speed of the animation. (Optional)
|
* @param {number} [speed] The speed of the animation.
|
||||||
*/
|
*/
|
||||||
updateDom: function (speed) {
|
updateDom: function (speed) {
|
||||||
MM.updateDom(this, speed);
|
MM.updateDom(this, speed);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* sendNotification(notification, payload)
|
/**
|
||||||
* Send a notification to all modules.
|
* Send a notification to all modules.
|
||||||
*
|
*
|
||||||
* argument notification string - The identifier of the notification.
|
* @param {string} notification The identifier of the notification.
|
||||||
* argument payload mixed - The payload of the notification.
|
* @param {*} payload The payload of the notification.
|
||||||
*/
|
*/
|
||||||
sendNotification: function (notification, payload) {
|
sendNotification: function (notification, payload) {
|
||||||
MM.sendNotification(notification, payload, this);
|
MM.sendNotification(notification, payload, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* sendSocketNotification(notification, payload)
|
/**
|
||||||
* Send a socket notification to the node helper.
|
* Send a socket notification to the node helper.
|
||||||
*
|
*
|
||||||
* argument notification string - The identifier of the notification.
|
* @param {string} notification The identifier of the notification.
|
||||||
* argument payload mixed - The payload of the notification.
|
* @param {*} payload The payload of the notification.
|
||||||
*/
|
*/
|
||||||
sendSocketNotification: function (notification, payload) {
|
sendSocketNotification: function (notification, payload) {
|
||||||
this.socket().sendNotification(notification, payload);
|
this.socket().sendNotification(notification, payload);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* hideModule(module, speed, callback)
|
/**
|
||||||
* Hide this module.
|
* Hide this module.
|
||||||
*
|
*
|
||||||
* argument speed Number - The speed of the hide animation.
|
* @param {number} speed The speed of the hide animation.
|
||||||
* argument callback function - Called when the animation is done.
|
* @param {Function} callback Called when the animation is done.
|
||||||
* argument options object - Optional settings for the hide method.
|
* @param {object} [options] Optional settings for the hide method.
|
||||||
*/
|
*/
|
||||||
hide: function (speed, callback, options) {
|
hide: function (speed, callback, options) {
|
||||||
if (typeof callback === "object") {
|
if (typeof callback === "object") {
|
||||||
@ -405,12 +411,12 @@ var Module = Class.extend({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* showModule(module, speed, callback)
|
/**
|
||||||
* Show this module.
|
* Show this module.
|
||||||
*
|
*
|
||||||
* argument speed Number - The speed of the show animation.
|
* @param {number} speed The speed of the show animation.
|
||||||
* argument callback function - Called when the animation is done.
|
* @param {Function} callback Called when the animation is done.
|
||||||
* argument options object - Optional settings for the hide method.
|
* @param {object} [options] Optional settings for the show method.
|
||||||
*/
|
*/
|
||||||
show: function (speed, callback, options) {
|
show: function (speed, callback, options) {
|
||||||
if (typeof callback === "object") {
|
if (typeof callback === "object") {
|
||||||
@ -451,11 +457,27 @@ Module.create = function (name) {
|
|||||||
return new ModuleClass();
|
return new ModuleClass();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* cmpVersions(a,b)
|
Module.register = function (name, moduleDefinition) {
|
||||||
|
if (moduleDefinition.requiresVersion) {
|
||||||
|
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version);
|
||||||
|
if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) {
|
||||||
|
Log.log("Version is ok!");
|
||||||
|
} else {
|
||||||
|
Log.log("Version is incorrect. Skip module: '" + name + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.log("Module registered: " + name);
|
||||||
|
Module.definitions[name] = moduleDefinition;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
* Compare two semantic version numbers and return the difference.
|
* Compare two semantic version numbers and return the difference.
|
||||||
*
|
*
|
||||||
* argument a string - Version number a.
|
* @param {string} a Version number a.
|
||||||
* argument a string - Version number b.
|
* @param {string} b Version number b.
|
||||||
|
* @returns {number} A positive number if a is larger than b, a negative
|
||||||
|
* number if a is smaller and 0 if they are the same
|
||||||
*/
|
*/
|
||||||
function cmpVersions(a, b) {
|
function cmpVersions(a, b) {
|
||||||
var i, diff;
|
var i, diff;
|
||||||
@ -472,17 +494,3 @@ function cmpVersions(a, b) {
|
|||||||
}
|
}
|
||||||
return segmentsA.length - segmentsB.length;
|
return segmentsA.length - segmentsB.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.register = function (name, moduleDefinition) {
|
|
||||||
if (moduleDefinition.requiresVersion) {
|
|
||||||
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version);
|
|
||||||
if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) {
|
|
||||||
Log.log("Version is ok!");
|
|
||||||
} else {
|
|
||||||
Log.log("Version is incorrect. Skip module: '" + name + "'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.log("Module registered: " + name);
|
|
||||||
Module.definitions[name] = moduleDefinition;
|
|
||||||
};
|
|
||||||
|
127
js/translator.js
@ -7,11 +7,11 @@
|
|||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
*/
|
*/
|
||||||
var Translator = (function () {
|
var Translator = (function () {
|
||||||
/* loadJSON(file, callback)
|
/**
|
||||||
* Load a JSON file via XHR.
|
* Load a JSON file via XHR.
|
||||||
*
|
*
|
||||||
* argument file string - Path of the file we want to load.
|
* @param {string} file Path of the file we want to load.
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
function loadJSON(file, callback) {
|
function loadJSON(file, callback) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
@ -19,112 +19,39 @@ var Translator = (function () {
|
|||||||
xhr.open("GET", file, true);
|
xhr.open("GET", file, true);
|
||||||
xhr.onreadystatechange = function () {
|
xhr.onreadystatechange = function () {
|
||||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||||
callback(JSON.parse(stripComments(xhr.responseText)));
|
callback(JSON.parse(xhr.responseText));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.send(null);
|
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 {
|
return {
|
||||||
coreTranslations: {},
|
coreTranslations: {},
|
||||||
coreTranslationsFallback: {},
|
coreTranslationsFallback: {},
|
||||||
translations: {},
|
translations: {},
|
||||||
translationsFallback: {},
|
translationsFallback: {},
|
||||||
|
|
||||||
/* translate(module, key, variables)
|
/**
|
||||||
* Load a translation for a given key for a given module.
|
* Load a translation for a given key for a given module.
|
||||||
*
|
*
|
||||||
* argument module Module - The module to load the translation for.
|
* @param {Module} module The module to load the translation for.
|
||||||
* argument key string - The key of the text to translate.
|
* @param {string} key The key of the text to translate.
|
||||||
* argument variables - The variables to use within the translation template (optional)
|
* @param {object} variables The variables to use within the translation template (optional)
|
||||||
|
* @returns {string} the translated key
|
||||||
*/
|
*/
|
||||||
translate: function (module, key, variables) {
|
translate: function (module, key, variables) {
|
||||||
variables = variables || {}; //Empty object by default
|
variables = variables || {}; //Empty object by default
|
||||||
|
|
||||||
// Combines template and variables like:
|
/**
|
||||||
// template: "Please wait for {timeToWait} before continuing with {work}."
|
* Combines template and variables like:
|
||||||
// variables: {timeToWait: "2 hours", work: "painting"}
|
* template: "Please wait for {timeToWait} before continuing with {work}."
|
||||||
// to: "Please wait for 2 hours before continuing with painting."
|
* variables: {timeToWait: "2 hours", work: "painting"}
|
||||||
|
* to: "Please wait for 2 hours before continuing with painting."
|
||||||
|
*
|
||||||
|
* @param {string} template Text with placeholder
|
||||||
|
* @param {object} variables Variables for the placeholder
|
||||||
|
* @returns {string} the template filled with the variables
|
||||||
|
*/
|
||||||
function createStringFromTemplate(template, variables) {
|
function createStringFromTemplate(template, variables) {
|
||||||
if (Object.prototype.toString.call(template) !== "[object String]") {
|
if (Object.prototype.toString.call(template) !== "[object String]") {
|
||||||
return template;
|
return template;
|
||||||
@ -160,13 +87,13 @@ var Translator = (function () {
|
|||||||
return key;
|
return key;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* load(module, file, isFallback, callback)
|
/**
|
||||||
* Load a translation file (json) and remember the data.
|
* Load a translation file (json) and remember the data.
|
||||||
*
|
*
|
||||||
* argument module Module - The module to load the translation file for.
|
* @param {Module} module The module to load the translation file for.
|
||||||
* argument file string - Path of the file we want to load.
|
* @param {string} file Path of the file we want to load.
|
||||||
* argument isFallback boolean - Flag to indicate fallback translations.
|
* @param {boolean} isFallback Flag to indicate fallback translations.
|
||||||
* argument callback function - Function called when done.
|
* @param {Function} callback Function called when done.
|
||||||
*/
|
*/
|
||||||
load: function (module, file, isFallback, callback) {
|
load: function (module, file, isFallback, callback) {
|
||||||
if (!isFallback) {
|
if (!isFallback) {
|
||||||
@ -190,10 +117,10 @@ var Translator = (function () {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/* loadCoreTranslations(lang)
|
/**
|
||||||
* Load the core translations.
|
* Load the core translations.
|
||||||
*
|
*
|
||||||
* argument lang String - The language identifier of the core language.
|
* @param {string} lang The language identifier of the core language.
|
||||||
*/
|
*/
|
||||||
loadCoreTranslations: function (lang) {
|
loadCoreTranslations: function (lang) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -210,7 +137,7 @@ var Translator = (function () {
|
|||||||
self.loadCoreTranslationsFallback();
|
self.loadCoreTranslationsFallback();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* loadCoreTranslationsFallback()
|
/**
|
||||||
* Load the core translations fallback.
|
* Load the core translations fallback.
|
||||||
* The first language defined in translations.js will be used.
|
* The first language defined in translations.js will be used.
|
||||||
*/
|
*/
|
||||||
|
@ -10,7 +10,8 @@ var Utils = {
|
|||||||
colors: {
|
colors: {
|
||||||
warn: colors.yellow,
|
warn: colors.yellow,
|
||||||
error: colors.red,
|
error: colors.red,
|
||||||
info: colors.blue
|
info: colors.blue,
|
||||||
|
pass: colors.green
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,7 +12,11 @@
|
|||||||
*/
|
*/
|
||||||
(function (window) {
|
(function (window) {
|
||||||
/**
|
/**
|
||||||
* extend obj function
|
* Extend one object with another one
|
||||||
|
*
|
||||||
|
* @param {object} a The object to extend
|
||||||
|
* @param {object} b The object which extends the other, overwrites existing keys
|
||||||
|
* @returns {object} The merged object
|
||||||
*/
|
*/
|
||||||
function extend(a, b) {
|
function extend(a, b) {
|
||||||
for (let key in b) {
|
for (let key in b) {
|
||||||
@ -24,7 +28,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NotificationFx function
|
* NotificationFx constructor
|
||||||
|
*
|
||||||
|
* @param {object} options The configuration options
|
||||||
|
* @class
|
||||||
*/
|
*/
|
||||||
function NotificationFx(options) {
|
function NotificationFx(options) {
|
||||||
this.options = extend({}, this.options);
|
this.options = extend({}, this.options);
|
||||||
@ -66,8 +73,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init function
|
* Initialize and cache some vars
|
||||||
* initialize and cache some vars
|
|
||||||
*/
|
*/
|
||||||
NotificationFx.prototype._init = function () {
|
NotificationFx.prototype._init = function () {
|
||||||
// create HTML structure
|
// create HTML structure
|
||||||
@ -95,7 +101,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init events
|
* Init events
|
||||||
*/
|
*/
|
||||||
NotificationFx.prototype._initEvents = function () {
|
NotificationFx.prototype._initEvents = function () {
|
||||||
// dismiss notification by tapping on it if someone has a touchscreen
|
// dismiss notification by tapping on it if someone has a touchscreen
|
||||||
@ -105,7 +111,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the notification
|
* Show the notification
|
||||||
*/
|
*/
|
||||||
NotificationFx.prototype.show = function () {
|
NotificationFx.prototype.show = function () {
|
||||||
this.active = true;
|
this.active = true;
|
||||||
@ -115,7 +121,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dismiss the notification
|
* Dismiss the notification
|
||||||
*/
|
*/
|
||||||
NotificationFx.prototype.dismiss = function () {
|
NotificationFx.prototype.dismiss = function () {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
@ -144,7 +150,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add to global namespace
|
* Add to global namespace
|
||||||
*/
|
*/
|
||||||
window.NotificationFx = NotificationFx;
|
window.NotificationFx = NotificationFx;
|
||||||
})(window);
|
})(window);
|
||||||
|
@ -205,7 +205,7 @@ Module.register("calendar", {
|
|||||||
eventWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
|
eventWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventWrapper.className = "normal";
|
eventWrapper.className = "normal event";
|
||||||
|
|
||||||
if (this.config.displaySymbol) {
|
if (this.config.displaySymbol) {
|
||||||
var symbolWrapper = document.createElement("td");
|
var symbolWrapper = document.createElement("td");
|
||||||
@ -217,11 +217,7 @@ Module.register("calendar", {
|
|||||||
var symbolClass = this.symbolClassForUrl(event.url);
|
var symbolClass = this.symbolClassForUrl(event.url);
|
||||||
symbolWrapper.className = "symbol align-right " + symbolClass;
|
symbolWrapper.className = "symbol align-right " + symbolClass;
|
||||||
|
|
||||||
var symbols = this.symbolsForUrl(event.url);
|
var symbols = this.symbolsForEvent(event);
|
||||||
if (typeof symbols === "string") {
|
|
||||||
symbols = [symbols];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < symbols.length; i++) {
|
for (var i = 0; i < symbols.length; i++) {
|
||||||
var symbol = document.createElement("span");
|
var symbol = document.createElement("span");
|
||||||
symbol.className = "fa fa-fw fa-" + symbols[i];
|
symbol.className = "fa fa-fw fa-" + symbols[i];
|
||||||
@ -230,6 +226,7 @@ Module.register("calendar", {
|
|||||||
}
|
}
|
||||||
symbolWrapper.appendChild(symbol);
|
symbolWrapper.appendChild(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventWrapper.appendChild(symbolWrapper);
|
eventWrapper.appendChild(symbolWrapper);
|
||||||
} else if (this.config.timeFormat === "dateheaders") {
|
} else if (this.config.timeFormat === "dateheaders") {
|
||||||
var blankCell = document.createElement("td");
|
var blankCell = document.createElement("td");
|
||||||
@ -419,7 +416,7 @@ Module.register("calendar", {
|
|||||||
* it will a localeSpecification object with the system locale time format.
|
* it will a localeSpecification object with the system locale time format.
|
||||||
*
|
*
|
||||||
* @param {number} timeFormat Specifies either 12 or 24 hour time format
|
* @param {number} timeFormat Specifies either 12 or 24 hour time format
|
||||||
* @returns {moment.LocaleSpecification}
|
* @returns {moment.LocaleSpecification} formatted time
|
||||||
*/
|
*/
|
||||||
getLocaleSpecification: function (timeFormat) {
|
getLocaleSpecification: function (timeFormat) {
|
||||||
switch (timeFormat) {
|
switch (timeFormat) {
|
||||||
@ -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.
|
* @param {string} url The calendar url
|
||||||
*
|
* @returns {boolean} True if the calendar config contains the url, False otherwise
|
||||||
* return bool - Has calendar url
|
|
||||||
*/
|
*/
|
||||||
hasCalendarURL: function (url) {
|
hasCalendarURL: function (url) {
|
||||||
for (var c in this.config.calendars) {
|
for (var c in this.config.calendars) {
|
||||||
@ -453,10 +449,10 @@ Module.register("calendar", {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* createEventList()
|
/**
|
||||||
* Creates the sorted list of all events.
|
* Creates the sorted list of all events.
|
||||||
*
|
*
|
||||||
* return array - Array with events.
|
* @returns {object[]} Array with events.
|
||||||
*/
|
*/
|
||||||
createEventList: function () {
|
createEventList: function () {
|
||||||
var events = [];
|
var events = [];
|
||||||
@ -467,6 +463,7 @@ Module.register("calendar", {
|
|||||||
var calendar = this.calendarData[c];
|
var calendar = this.calendarData[c];
|
||||||
for (var e in calendar) {
|
for (var e in calendar) {
|
||||||
var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
|
var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
|
||||||
|
|
||||||
if (event.endDate < now) {
|
if (event.endDate < now) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -536,10 +533,12 @@ Module.register("calendar", {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* createEventList(url)
|
/**
|
||||||
* Requests node helper to add calendar url.
|
* Requests node helper to add calendar url.
|
||||||
*
|
*
|
||||||
* argument url string - Url to add.
|
* @param {string} url The calendar url to add
|
||||||
|
* @param {object} auth The authentication method and credentials
|
||||||
|
* @param {object} calendarConfig The config of the specific calendar
|
||||||
*/
|
*/
|
||||||
addCalendar: function (url, auth, calendarConfig) {
|
addCalendar: function (url, auth, calendarConfig) {
|
||||||
this.sendSocketNotification("ADD_CALENDAR", {
|
this.sendSocketNotification("ADD_CALENDAR", {
|
||||||
@ -558,94 +557,100 @@ Module.register("calendar", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* symbolsForUrl(url)
|
* Retrieves the symbols for a specific event.
|
||||||
* Retrieves the symbols for a specific url.
|
|
||||||
*
|
*
|
||||||
* argument url string - Url to look for.
|
* @param {object} event Event to look for.
|
||||||
*
|
* @returns {string[]} The symbols
|
||||||
* return string/array - The Symbols
|
|
||||||
*/
|
*/
|
||||||
symbolsForUrl: function (url) {
|
symbolsForEvent: function (event) {
|
||||||
return this.getCalendarProperty(url, "symbol", this.config.defaultSymbol);
|
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 calendar url.
|
||||||
* Retrieves the symbolClass for a specific url.
|
|
||||||
*
|
*
|
||||||
* @param url string - Url to look for.
|
* @param {string} url The calendar url
|
||||||
*
|
* @returns {string} The class to be used for the symbols of the calendar
|
||||||
* @returns string
|
|
||||||
*/
|
*/
|
||||||
symbolClassForUrl: function (url) {
|
symbolClassForUrl: function (url) {
|
||||||
return this.getCalendarProperty(url, "symbolClass", "");
|
return this.getCalendarProperty(url, "symbolClass", "");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* titleClassForUrl(url)
|
* Retrieves the titleClass for a specific calendar url.
|
||||||
* Retrieves the titleClass for a specific url.
|
|
||||||
*
|
*
|
||||||
* @param url string - Url to look for.
|
* @param {string} url The calendar url
|
||||||
*
|
* @returns {string} The class to be used for the title of the calendar
|
||||||
* @returns string
|
|
||||||
*/
|
*/
|
||||||
titleClassForUrl: function (url) {
|
titleClassForUrl: function (url) {
|
||||||
return this.getCalendarProperty(url, "titleClass", "");
|
return this.getCalendarProperty(url, "titleClass", "");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* timeClassForUrl(url)
|
* Retrieves the timeClass for a specific calendar url.
|
||||||
* Retrieves the timeClass for a specific url.
|
|
||||||
*
|
*
|
||||||
* @param url string - Url to look for.
|
* @param {string} url The calendar url
|
||||||
*
|
* @returns {string} The class to be used for the time of the calendar
|
||||||
* @returns string
|
|
||||||
*/
|
*/
|
||||||
timeClassForUrl: function (url) {
|
timeClassForUrl: function (url) {
|
||||||
return this.getCalendarProperty(url, "timeClass", "");
|
return this.getCalendarProperty(url, "timeClass", "");
|
||||||
},
|
},
|
||||||
|
|
||||||
/* calendarNameForUrl(url)
|
/**
|
||||||
* Retrieves the calendar name for a specific url.
|
* Retrieves the calendar name for a specific calendar url.
|
||||||
*
|
*
|
||||||
* argument url string - Url to look for.
|
* @param {string} url The calendar url
|
||||||
*
|
* @returns {string} The name of the calendar
|
||||||
* return string - The name of the calendar
|
|
||||||
*/
|
*/
|
||||||
calendarNameForUrl: function (url) {
|
calendarNameForUrl: function (url) {
|
||||||
return this.getCalendarProperty(url, "name", "");
|
return this.getCalendarProperty(url, "name", "");
|
||||||
},
|
},
|
||||||
|
|
||||||
/* colorForUrl(url)
|
/**
|
||||||
* Retrieves the color for a specific url.
|
* Retrieves the color for a specific calendar url.
|
||||||
*
|
*
|
||||||
* argument url string - Url to look for.
|
* @param {string} url The calendar url
|
||||||
*
|
* @returns {string} The color
|
||||||
* return string - The Color
|
|
||||||
*/
|
*/
|
||||||
colorForUrl: function (url) {
|
colorForUrl: function (url) {
|
||||||
return this.getCalendarProperty(url, "color", "#fff");
|
return this.getCalendarProperty(url, "color", "#fff");
|
||||||
},
|
},
|
||||||
|
|
||||||
/* countTitleForUrl(url)
|
/**
|
||||||
* Retrieves the name for a specific url.
|
* Retrieves the count title for a specific calendar url.
|
||||||
*
|
*
|
||||||
* argument url string - Url to look for.
|
* @param {string} url The calendar url
|
||||||
*
|
* @returns {string} The title
|
||||||
* return string - The Symbol
|
|
||||||
*/
|
*/
|
||||||
countTitleForUrl: function (url) {
|
countTitleForUrl: function (url) {
|
||||||
return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle);
|
return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* getCalendarProperty(url, property, defaultValue)
|
/**
|
||||||
* Helper method to retrieve the property for a specific url.
|
* Helper method to retrieve the property for a specific calendar url.
|
||||||
*
|
*
|
||||||
* argument url string - Url to look for.
|
* @param {string} url The calendar url
|
||||||
* argument property string - Property to look for.
|
* @param {string} property The property to look for
|
||||||
* argument defaultValue string - Value if property is not found.
|
* @param {string} defaultValue The value if the property is not found
|
||||||
*
|
* @returns {*} The property
|
||||||
* return string - The Property
|
|
||||||
*/
|
*/
|
||||||
getCalendarProperty: function (url, property, defaultValue) {
|
getCalendarProperty: function (url, property, defaultValue) {
|
||||||
for (var c in this.config.calendars) {
|
for (var c in this.config.calendars) {
|
||||||
@ -658,6 +663,16 @@ Module.register("calendar", {
|
|||||||
return defaultValue;
|
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
|
* 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
|
* Capitalize the first letter of a string
|
||||||
* Return capitalized string
|
*
|
||||||
|
* @param {string} string The string to capitalize
|
||||||
|
* @returns {string} The capitalized string
|
||||||
*/
|
*/
|
||||||
capFirst: function (string) {
|
capFirst: function (string) {
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* titleTransform(title)
|
/**
|
||||||
* Transforms the title of an event for usage.
|
* Transforms the title of an event for usage.
|
||||||
* Replaces parts of the text as defined in config.titleReplace.
|
* Replaces parts of the text as defined in config.titleReplace.
|
||||||
* Shortens title based on config.maxTitleLength and config.wrapEvents
|
* Shortens title based on config.maxTitleLength and config.wrapEvents
|
||||||
*
|
*
|
||||||
* argument title string - The title to transform.
|
* @param {string} title The title to transform.
|
||||||
*
|
* @param {object} titleReplace Pairs of strings to be replaced in the title
|
||||||
* return string - The transformed title.
|
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
|
||||||
|
* @param {number} maxTitleLength The max length of the string
|
||||||
|
* @param {number} maxTitleLines The max number of vertical lines before cutting event title
|
||||||
|
* @returns {string} The transformed title.
|
||||||
*/
|
*/
|
||||||
titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) {
|
titleTransform: function (title, titleReplace, wrapEvents, maxTitleLength, maxTitleLines) {
|
||||||
for (var needle in titleReplace) {
|
for (var needle in titleReplace) {
|
||||||
@ -745,7 +765,7 @@ Module.register("calendar", {
|
|||||||
return title;
|
return title;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* broadcastEvents()
|
/**
|
||||||
* Broadcasts the events to all other modules for reuse.
|
* Broadcasts the events to all other modules for reuse.
|
||||||
* The all events available in one array, sorted on startdate.
|
* The all events available in one array, sorted on startdate.
|
||||||
*/
|
*/
|
||||||
@ -755,7 +775,7 @@ Module.register("calendar", {
|
|||||||
var calendar = this.calendarData[url];
|
var calendar = this.calendarData[url];
|
||||||
for (var e in calendar) {
|
for (var e in calendar) {
|
||||||
var event = cloneObject(calendar[e]);
|
var event = cloneObject(calendar[e]);
|
||||||
event.symbol = this.symbolsForUrl(url);
|
event.symbol = this.symbolsForEvent(event);
|
||||||
event.calendarName = this.calendarNameForUrl(url);
|
event.calendarName = this.calendarNameForUrl(url);
|
||||||
event.color = this.colorForUrl(url);
|
event.color = this.colorForUrl(url);
|
||||||
delete event.url;
|
delete event.url;
|
||||||
|
@ -6,10 +6,28 @@
|
|||||||
*/
|
*/
|
||||||
const Log = require("../../../js/logger.js");
|
const Log = require("../../../js/logger.js");
|
||||||
const ical = require("ical");
|
const ical = require("ical");
|
||||||
const moment = require("moment");
|
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
|
|
||||||
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;
|
const self = this;
|
||||||
|
|
||||||
let reloadTimer = null;
|
let reloadTimer = null;
|
||||||
@ -18,7 +36,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu
|
|||||||
let fetchFailedCallback = function () {};
|
let fetchFailedCallback = function () {};
|
||||||
let eventsReceivedCallback = function () {};
|
let eventsReceivedCallback = function () {};
|
||||||
|
|
||||||
/* fetchCalendar()
|
/**
|
||||||
* Initiates calendar fetch.
|
* Initiates calendar fetch.
|
||||||
*/
|
*/
|
||||||
const fetchCalendar = function () {
|
const fetchCalendar = function () {
|
||||||
@ -267,6 +285,7 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu
|
|||||||
startDate: startDate.format("x"),
|
startDate: startDate.format("x"),
|
||||||
endDate: endDate.format("x"),
|
endDate: endDate.format("x"),
|
||||||
fullDayEvent: isFullDayEvent(event),
|
fullDayEvent: isFullDayEvent(event),
|
||||||
|
recurringEvent: true,
|
||||||
class: event.class,
|
class: event.class,
|
||||||
firstYear: event.start.getFullYear(),
|
firstYear: event.start.getFullYear(),
|
||||||
location: location,
|
location: location,
|
||||||
@ -330,14 +349,14 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu
|
|||||||
return a.startDate - b.startDate;
|
return a.startDate - b.startDate;
|
||||||
});
|
});
|
||||||
|
|
||||||
events = newEvents;
|
events = newEvents.slice(0, maximumEntries);
|
||||||
|
|
||||||
self.broadcastEvents();
|
self.broadcastEvents();
|
||||||
scheduleTimer();
|
scheduleTimer();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* scheduleTimer()
|
/**
|
||||||
* Schedule the timer for the next update.
|
* Schedule the timer for the next update.
|
||||||
*/
|
*/
|
||||||
const scheduleTimer = function () {
|
const scheduleTimer = function () {
|
||||||
@ -347,12 +366,11 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu
|
|||||||
}, reloadInterval);
|
}, reloadInterval);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* isFullDayEvent(event)
|
/**
|
||||||
* Checks if an event is a fullday event.
|
* Checks if an event is a fullday event.
|
||||||
*
|
*
|
||||||
* argument event object - The event object to check.
|
* @param {object} event The event object to check.
|
||||||
*
|
* @returns {boolean} True if the event is a fullday event, false otherwise
|
||||||
* return bool - The event is a fullday event.
|
|
||||||
*/
|
*/
|
||||||
const isFullDayEvent = function (event) {
|
const isFullDayEvent = function (event) {
|
||||||
if (event.start.length === 8 || event.start.dateOnly) {
|
if (event.start.length === 8 || event.start.dateOnly) {
|
||||||
@ -370,14 +388,13 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* timeFilterApplies()
|
/**
|
||||||
* Determines if the user defined time filter should apply
|
* Determines if the user defined time filter should apply
|
||||||
*
|
*
|
||||||
* argument now Date - Date object using previously created object for consistency
|
* @param {Date} now Date object using previously created object for consistency
|
||||||
* argument endDate Moment - Moment object representing the event end date
|
* @param {Moment} endDate Moment object representing the event end date
|
||||||
* argument filter string - The time to subtract from the end date to determine if an event should be shown
|
* @param {string} filter The time to subtract from the end date to determine if an event should be shown
|
||||||
*
|
* @returns {boolean} True if the event should be filtered out, false otherwise
|
||||||
* return bool - The event should be filtered out
|
|
||||||
*/
|
*/
|
||||||
const timeFilterApplies = function (now, endDate, filter) {
|
const timeFilterApplies = function (now, endDate, filter) {
|
||||||
if (filter) {
|
if (filter) {
|
||||||
@ -392,12 +409,11 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* getTitleFromEvent(event)
|
/**
|
||||||
* Gets the title from the event.
|
* Gets the title from the event.
|
||||||
*
|
*
|
||||||
* argument event object - The event object to check.
|
* @param {object} event The event object to check.
|
||||||
*
|
* @returns {string} The title of the event, or "Event" if no title is found.
|
||||||
* return string - The title of the event, or "Event" if no title is found.
|
|
||||||
*/
|
*/
|
||||||
const getTitleFromEvent = function (event) {
|
const getTitleFromEvent = function (event) {
|
||||||
let title = "Event";
|
let title = "Event";
|
||||||
@ -428,14 +444,14 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu
|
|||||||
|
|
||||||
/* public methods */
|
/* public methods */
|
||||||
|
|
||||||
/* startFetch()
|
/**
|
||||||
* Initiate fetchCalendar();
|
* Initiate fetchCalendar();
|
||||||
*/
|
*/
|
||||||
this.startFetch = function () {
|
this.startFetch = function () {
|
||||||
fetchCalendar();
|
fetchCalendar();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* broadcastItems()
|
/**
|
||||||
* Broadcast the existing events.
|
* Broadcast the existing events.
|
||||||
*/
|
*/
|
||||||
this.broadcastEvents = function () {
|
this.broadcastEvents = function () {
|
||||||
@ -443,37 +459,37 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumNu
|
|||||||
eventsReceivedCallback(self);
|
eventsReceivedCallback(self);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* onReceive(callback)
|
/**
|
||||||
* Sets the on success callback
|
* Sets the on success callback
|
||||||
*
|
*
|
||||||
* argument callback function - The on success callback.
|
* @param {Function} callback The on success callback.
|
||||||
*/
|
*/
|
||||||
this.onReceive = function (callback) {
|
this.onReceive = function (callback) {
|
||||||
eventsReceivedCallback = callback;
|
eventsReceivedCallback = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* onError(callback)
|
/**
|
||||||
* Sets the on error callback
|
* Sets the on error callback
|
||||||
*
|
*
|
||||||
* argument callback function - The on error callback.
|
* @param {Function} callback The on error callback.
|
||||||
*/
|
*/
|
||||||
this.onError = function (callback) {
|
this.onError = function (callback) {
|
||||||
fetchFailedCallback = callback;
|
fetchFailedCallback = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* url()
|
/**
|
||||||
* Returns the url of this fetcher.
|
* Returns the url of this fetcher.
|
||||||
*
|
*
|
||||||
* return string - The url of this fetcher.
|
* @returns {string} The url of this fetcher.
|
||||||
*/
|
*/
|
||||||
this.url = function () {
|
this.url = function () {
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* events()
|
/**
|
||||||
* Returns current available events for this fetcher.
|
* Returns current available events for this fetcher.
|
||||||
*
|
*
|
||||||
* return array - The current available events for this fetcher.
|
* @returns {object[]} The current available events for this fetcher.
|
||||||
*/
|
*/
|
||||||
this.events = function () {
|
this.events = function () {
|
||||||
return events;
|
return events;
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
* By Michael Teeuw https://michaelteeuw.nl
|
* By Michael Teeuw https://michaelteeuw.nl
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const NodeHelper = require("node_helper");
|
const NodeHelper = require("node_helper");
|
||||||
const validUrl = require("valid-url");
|
const validUrl = require("valid-url");
|
||||||
const CalendarFetcher = require("./calendarfetcher.js");
|
const CalendarFetcher = require("./calendarfetcher.js");
|
||||||
@ -20,18 +19,24 @@ module.exports = NodeHelper.create({
|
|||||||
// Override socketNotificationReceived method.
|
// Override socketNotificationReceived method.
|
||||||
socketNotificationReceived: function (notification, payload) {
|
socketNotificationReceived: function (notification, payload) {
|
||||||
if (notification === "ADD_CALENDAR") {
|
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.
|
* Creates a fetcher for a new url if it doesn't exist yet.
|
||||||
* Otherwise it reuses the existing one.
|
* Otherwise it reuses the existing one.
|
||||||
*
|
*
|
||||||
* attribute url string - URL of the news feed.
|
* @param {string} url The url of the calendar
|
||||||
* attribute reloadInterval number - Reload interval in milliseconds.
|
* @param {number} fetchInterval How often does the calendar needs to be fetched in ms
|
||||||
|
* @param {string[]} excludedEvents An array of words / phrases from event titles that will be excluded from being shown.
|
||||||
|
* @param {number} maximumEntries The maximum number of events fetched.
|
||||||
|
* @param {number} maximumNumberOfDays The maximum number of days an event should be in the future.
|
||||||
|
* @param {object} auth The object containing options for authentication against the calendar.
|
||||||
|
* @param {boolean} broadcastPastEvents If true events from the past maximumNumberOfDays will be included in event broadcasts
|
||||||
|
* @param {string} identifier ID of the module
|
||||||
*/
|
*/
|
||||||
createFetcher: function (url, fetchInterval, excludedEvents, maximumNumberOfDays, auth, broadcastPastEvents, identifier) {
|
createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, identifier) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (!validUrl.isUri(url)) {
|
if (!validUrl.isUri(url)) {
|
||||||
@ -42,7 +47,7 @@ module.exports = NodeHelper.create({
|
|||||||
var fetcher;
|
var fetcher;
|
||||||
if (typeof self.fetchers[identifier + url] === "undefined") {
|
if (typeof self.fetchers[identifier + url] === "undefined") {
|
||||||
Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
|
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) {
|
fetcher.onReceive(function (fetcher) {
|
||||||
self.sendSocketNotification("CALENDAR_EVENTS", {
|
self.sendSocketNotification("CALENDAR_EVENTS", {
|
||||||
|
@ -152,6 +152,13 @@ Module.register("clock", {
|
|||||||
timeWrapper.appendChild(periodWrapper);
|
timeWrapper.appendChild(periodWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the time according to the config
|
||||||
|
*
|
||||||
|
* @param {object} config The config of the module
|
||||||
|
* @param {object} time time to format
|
||||||
|
* @returns {string} The formatted time string
|
||||||
|
*/
|
||||||
function formatTime(config, time) {
|
function formatTime(config, time) {
|
||||||
var formatString = hourSymbol + ":mm";
|
var formatString = hourSymbol + ":mm";
|
||||||
if (config.showPeriod && config.timeFormat !== 24) {
|
if (config.showPeriod && config.timeFormat !== 24) {
|
||||||
@ -159,6 +166,7 @@ Module.register("clock", {
|
|||||||
}
|
}
|
||||||
return moment(time).format(formatString);
|
return moment(time).format(formatString);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.showSunTimes) {
|
if (this.config.showSunTimes) {
|
||||||
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
|
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
|
||||||
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
|
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
|
||||||
|
@ -1 +1 @@
|
|||||||
<svg id="Hour_Markers_-_Singlets" data-name="Hour Markers - Singlets" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{stroke-width:0.5px;}</style></defs><title>face-001</title><line class="cls-1" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-1" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-1" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-1" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-1" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-1" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-1" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-1" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-1" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-1" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-1" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-1" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><line class="cls-2" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-2" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-2" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-2" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-2" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-2" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-2" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-2" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-2" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-2" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-2" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-2" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-2" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-2" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-2" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-2" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-2" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-2" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-2" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-2" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-2" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-2" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-2" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-2" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-2" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-2" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-2" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-2" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-2" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-2" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-2" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-2" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-2" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-2" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-2" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-2" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-2" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-2" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-2" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-2" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-2" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-2" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-2" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-2" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-2" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-2" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-2" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-2" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/></svg>
|
<svg id="Hour_Markers_-_Singlets" data-name="Hour Markers - Singlets" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{stroke-width:0.5px;}</style></defs><title>face-001</title><line class="cls-1" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-1" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-1" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-1" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-1" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-1" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-1" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-1" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-1" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-1" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-1" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-1" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><line class="cls-2" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-2" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-2" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-2" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-2" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-2" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-2" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-2" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-2" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-2" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-2" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-2" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-2" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-2" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-2" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-2" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-2" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-2" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-2" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-2" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-2" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-2" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-2" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-2" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-2" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-2" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-2" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-2" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-2" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-2" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-2" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-2" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-2" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-2" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-2" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-2" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-2" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-2" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-2" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-2" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-2" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-2" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-2" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-2" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-2" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-2" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-2" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-2" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/></svg>
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@ -1 +1 @@
|
|||||||
<svg id="Hour_Markers_-_Doubles" data-name="Hour Markers - Doubles" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;stroke-width:2.98px;}</style></defs><title>face-002</title><line class="cls-1" x1="122.01" y1="1.75" x2="122.01" y2="16.67"/><line class="cls-1" x1="186.62" y1="18.26" x2="179.17" y2="31.18"/><line class="cls-1" x1="231.74" y1="63.37" x2="218.82" y2="70.83"/><line class="cls-1" x1="248.25" y1="127.99" x2="233.33" y2="127.99"/><line class="cls-1" x1="231.74" y1="186.62" x2="218.82" y2="179.17"/><line class="cls-1" x1="186.63" y1="231.74" x2="179.17" y2="218.82"/><line class="cls-1" x1="127.99" y1="248.25" x2="127.99" y2="233.33"/><line class="cls-1" x1="63.38" y1="231.74" x2="70.83" y2="218.82"/><line class="cls-1" x1="18.26" y1="186.63" x2="31.18" y2="179.17"/><line class="cls-1" x1="1.75" y1="122.01" x2="16.67" y2="122.01"/><line class="cls-1" x1="18.26" y1="63.38" x2="31.18" y2="70.83"/><line class="cls-1" x1="63.37" y1="18.26" x2="70.83" y2="31.18"/><line class="cls-1" x1="127.99" y1="1.75" x2="127.99" y2="16.67"/><line class="cls-1" x1="248.25" y1="122.01" x2="233.33" y2="122.01"/><line class="cls-1" x1="122.01" y1="248.25" x2="122.01" y2="233.33"/><line class="cls-1" x1="1.75" y1="127.99" x2="16.67" y2="127.99"/></svg>
|
<svg id="Hour_Markers_-_Doubles" data-name="Hour Markers - Doubles" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;stroke-width:2.98px;}</style></defs><title>face-002</title><line class="cls-1" x1="122.01" y1="1.75" x2="122.01" y2="16.67"/><line class="cls-1" x1="186.62" y1="18.26" x2="179.17" y2="31.18"/><line class="cls-1" x1="231.74" y1="63.37" x2="218.82" y2="70.83"/><line class="cls-1" x1="248.25" y1="127.99" x2="233.33" y2="127.99"/><line class="cls-1" x1="231.74" y1="186.62" x2="218.82" y2="179.17"/><line class="cls-1" x1="186.63" y1="231.74" x2="179.17" y2="218.82"/><line class="cls-1" x1="127.99" y1="248.25" x2="127.99" y2="233.33"/><line class="cls-1" x1="63.38" y1="231.74" x2="70.83" y2="218.82"/><line class="cls-1" x1="18.26" y1="186.63" x2="31.18" y2="179.17"/><line class="cls-1" x1="1.75" y1="122.01" x2="16.67" y2="122.01"/><line class="cls-1" x1="18.26" y1="63.38" x2="31.18" y2="70.83"/><line class="cls-1" x1="63.37" y1="18.26" x2="70.83" y2="31.18"/><line class="cls-1" x1="127.99" y1="1.75" x2="127.99" y2="16.67"/><line class="cls-1" x1="248.25" y1="122.01" x2="233.33" y2="122.01"/><line class="cls-1" x1="122.01" y1="248.25" x2="122.01" y2="233.33"/><line class="cls-1" x1="1.75" y1="127.99" x2="16.67" y2="127.99"/></svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@ -84,11 +84,11 @@ Module.register("newsfeed", {
|
|||||||
|
|
||||||
// Override dom generator.
|
// Override dom generator.
|
||||||
getDom: function () {
|
getDom: function () {
|
||||||
var wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
|
|
||||||
if (this.config.feedUrl) {
|
if (this.config.feedUrl) {
|
||||||
wrapper.className = "small bright";
|
wrapper.className = "small bright";
|
||||||
wrapper.innerHTML = this.translate("configuration_changed");
|
wrapper.innerHTML = this.translate("MODULE_CONFIG_CHANGED", { MODULE_NAME: "Newsfeed" });
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ Module.register("newsfeed", {
|
|||||||
if (this.newsItems.length > 0) {
|
if (this.newsItems.length > 0) {
|
||||||
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications
|
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications
|
||||||
if (!this.config.showFullArticle && (this.config.showSourceTitle || this.config.showPublishDate)) {
|
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";
|
sourceAndTimestamp.className = "newsfeed-source light small dimmed";
|
||||||
|
|
||||||
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") {
|
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") {
|
||||||
@ -157,22 +157,22 @@ Module.register("newsfeed", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.config.showFullArticle) {
|
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.className = "newsfeed-title bright medium light" + (!this.config.wrapTitle ? " no-wrap" : "");
|
||||||
title.innerHTML = this.newsItems[this.activeItem].title;
|
title.innerHTML = this.newsItems[this.activeItem].title;
|
||||||
wrapper.appendChild(title);
|
wrapper.appendChild(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isShowingDescription) {
|
if (this.isShowingDescription) {
|
||||||
var description = document.createElement("div");
|
const description = document.createElement("div");
|
||||||
description.className = "newsfeed-desc small light" + (!this.config.wrapDescription ? " no-wrap" : "");
|
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;
|
description.innerHTML = this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc;
|
||||||
wrapper.appendChild(description);
|
wrapper.appendChild(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.showFullArticle) {
|
if (this.config.showFullArticle) {
|
||||||
var fullArticle = document.createElement("iframe");
|
const fullArticle = document.createElement("iframe");
|
||||||
fullArticle.className = "";
|
fullArticle.className = "";
|
||||||
fullArticle.style.width = "100vw";
|
fullArticle.style.width = "100vw";
|
||||||
// very large height value to allow scrolling
|
// 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;
|
return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* registerFeeds()
|
/**
|
||||||
* registers the feeds to be used by the backend.
|
* Registers the feeds to be used by the backend.
|
||||||
*/
|
*/
|
||||||
registerFeeds: function () {
|
registerFeeds: function () {
|
||||||
for (var f in this.config.feeds) {
|
for (var f in this.config.feeds) {
|
||||||
@ -218,10 +218,10 @@ Module.register("newsfeed", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/* generateFeed()
|
/**
|
||||||
* Generate an ordered list of items for this configured module.
|
* Generate an ordered list of items for this configured module.
|
||||||
*
|
*
|
||||||
* attribute feeds object - An object with feeds returned by the node helper.
|
* @param {object} feeds An object with feeds returned by the node helper.
|
||||||
*/
|
*/
|
||||||
generateFeed: function (feeds) {
|
generateFeed: function (feeds) {
|
||||||
var newsItems = [];
|
var newsItems = [];
|
||||||
@ -274,12 +274,11 @@ Module.register("newsfeed", {
|
|||||||
this.newsItems = newsItems;
|
this.newsItems = newsItems;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* subscribedToFeed(feedUrl)
|
/**
|
||||||
* Check if this module is configured to show this feed.
|
* Check if this module is configured to show this feed.
|
||||||
*
|
*
|
||||||
* attribute feedUrl string - Url of the feed to check.
|
* @param {string} feedUrl Url of the feed to check.
|
||||||
*
|
* @returns {boolean} True if it is subscribed, false otherwise
|
||||||
* returns bool
|
|
||||||
*/
|
*/
|
||||||
subscribedToFeed: function (feedUrl) {
|
subscribedToFeed: function (feedUrl) {
|
||||||
for (var f in this.config.feeds) {
|
for (var f in this.config.feeds) {
|
||||||
@ -291,12 +290,11 @@ Module.register("newsfeed", {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* titleForFeed(feedUrl)
|
/**
|
||||||
* Returns title for a specific feed Url.
|
* Returns title for the specific feed url.
|
||||||
*
|
*
|
||||||
* attribute feedUrl string - Url of the feed to check.
|
* @param {string} feedUrl Url of the feed
|
||||||
*
|
* @returns {string} The title of the feed
|
||||||
* returns string
|
|
||||||
*/
|
*/
|
||||||
titleForFeed: function (feedUrl) {
|
titleForFeed: function (feedUrl) {
|
||||||
for (var f in this.config.feeds) {
|
for (var f in this.config.feeds) {
|
||||||
@ -308,7 +306,7 @@ Module.register("newsfeed", {
|
|||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
|
|
||||||
/* scheduleUpdateInterval()
|
/**
|
||||||
* Schedule visual update.
|
* Schedule visual update.
|
||||||
*/
|
*/
|
||||||
scheduleUpdateInterval: function () {
|
scheduleUpdateInterval: function () {
|
||||||
@ -332,17 +330,6 @@ Module.register("newsfeed", {
|
|||||||
}, this.config.updateInterval);
|
}, 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 () {
|
resetDescrOrFullArticleAndTimer: function () {
|
||||||
this.isShowingDescription = this.config.showDescription;
|
this.isShowingDescription = this.config.showDescription;
|
||||||
this.config.showFullArticle = false;
|
this.config.showFullArticle = false;
|
||||||
@ -356,7 +343,7 @@ Module.register("newsfeed", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
notificationReceived: function (notification, payload, sender) {
|
notificationReceived: function (notification, payload, sender) {
|
||||||
var before = this.activeItem;
|
const before = this.activeItem;
|
||||||
if (notification === "ARTICLE_NEXT") {
|
if (notification === "ARTICLE_NEXT") {
|
||||||
this.activeItem++;
|
this.activeItem++;
|
||||||
if (this.activeItem >= this.newsItems.length) {
|
if (this.activeItem >= this.newsItems.length) {
|
||||||
|
@ -1,55 +1,56 @@
|
|||||||
/* Magic Mirror
|
/* Magic Mirror
|
||||||
* Fetcher
|
* Node Helper: Newsfeed - NewsfeedFetcher
|
||||||
*
|
*
|
||||||
* By Michael Teeuw https://michaelteeuw.nl
|
* By Michael Teeuw https://michaelteeuw.nl
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const Log = require("../../../js/logger.js");
|
const Log = require("../../../js/logger.js");
|
||||||
const FeedMe = require("feedme");
|
const FeedMe = require("feedme");
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
const iconv = require("iconv-lite");
|
const iconv = require("iconv-lite");
|
||||||
|
|
||||||
/* Fetcher
|
/**
|
||||||
* Responsible for requesting an update on the set interval and broadcasting the data.
|
* Responsible for requesting an update on the set interval and broadcasting the data.
|
||||||
*
|
*
|
||||||
* attribute url string - URL of the news feed.
|
* @param {string} url URL of the news feed.
|
||||||
* attribute reloadInterval number - Reload interval in milliseconds.
|
* @param {number} reloadInterval Reload interval in milliseconds.
|
||||||
* attribute logFeedWarnings boolean - Log warnings when there is an error parsing a news article.
|
* @param {string} encoding Encoding of the feed.
|
||||||
|
* @param {boolean} logFeedWarnings If true log warnings when there is an error parsing a news article.
|
||||||
|
* @class
|
||||||
*/
|
*/
|
||||||
|
const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
|
||||||
|
const 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) {
|
if (reloadInterval < 1000) {
|
||||||
reloadInterval = 1000;
|
reloadInterval = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
var reloadTimer = null;
|
|
||||||
var items = [];
|
|
||||||
|
|
||||||
var fetchFailedCallback = function () {};
|
|
||||||
var itemsReceivedCallback = function () {};
|
|
||||||
|
|
||||||
/* private methods */
|
/* private methods */
|
||||||
|
|
||||||
/* fetchNews()
|
/**
|
||||||
* Request the new items.
|
* Request the new items.
|
||||||
*/
|
*/
|
||||||
var fetchNews = function () {
|
const fetchNews = function () {
|
||||||
clearTimeout(reloadTimer);
|
clearTimeout(reloadTimer);
|
||||||
reloadTimer = null;
|
reloadTimer = null;
|
||||||
items = [];
|
items = [];
|
||||||
|
|
||||||
var parser = new FeedMe();
|
const parser = new FeedMe();
|
||||||
|
|
||||||
parser.on("item", function (item) {
|
parser.on("item", function (item) {
|
||||||
var title = item.title;
|
const title = item.title;
|
||||||
var description = item.description || item.summary || item.content || "";
|
let description = item.description || item.summary || item.content || "";
|
||||||
var pubdate = item.pubdate || item.published || item.updated || item["dc:date"];
|
const pubdate = item.pubdate || item.published || item.updated || item["dc:date"];
|
||||||
var url = item.url || item.link || "";
|
const url = item.url || item.link || "";
|
||||||
|
|
||||||
if (title && pubdate) {
|
if (title && pubdate) {
|
||||||
var regex = /(<([^>]+)>)/gi;
|
const regex = /(<([^>]+)>)/gi;
|
||||||
description = description.toString().replace(regex, "");
|
description = description.toString().replace(regex, "");
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
@ -77,10 +78,17 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
|
|||||||
scheduleTimer();
|
scheduleTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
const 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 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) {
|
.on("error", function (error) {
|
||||||
fetchFailedCallback(self, error);
|
fetchFailedCallback(self, error);
|
||||||
scheduleTimer();
|
scheduleTimer();
|
||||||
@ -89,10 +97,10 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
|
|||||||
.pipe(parser);
|
.pipe(parser);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* scheduleTimer()
|
/**
|
||||||
* Schedule the timer for the next update.
|
* Schedule the timer for the next update.
|
||||||
*/
|
*/
|
||||||
var scheduleTimer = function () {
|
const scheduleTimer = function () {
|
||||||
clearTimeout(reloadTimer);
|
clearTimeout(reloadTimer);
|
||||||
reloadTimer = setTimeout(function () {
|
reloadTimer = setTimeout(function () {
|
||||||
fetchNews();
|
fetchNews();
|
||||||
@ -101,10 +109,10 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
|
|||||||
|
|
||||||
/* public methods */
|
/* public methods */
|
||||||
|
|
||||||
/* setReloadInterval()
|
/**
|
||||||
* Update the reload interval, but only if we need to increase the speed.
|
* Update the reload interval, but only if we need to increase the speed.
|
||||||
*
|
*
|
||||||
* attribute interval number - Interval for the update in milliseconds.
|
* @param {number} interval Interval for the update in milliseconds.
|
||||||
*/
|
*/
|
||||||
this.setReloadInterval = function (interval) {
|
this.setReloadInterval = function (interval) {
|
||||||
if (interval > 1000 && interval < reloadInterval) {
|
if (interval > 1000 && interval < reloadInterval) {
|
||||||
@ -112,14 +120,14 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* startFetch()
|
/**
|
||||||
* Initiate fetchNews();
|
* Initiate fetchNews();
|
||||||
*/
|
*/
|
||||||
this.startFetch = function () {
|
this.startFetch = function () {
|
||||||
fetchNews();
|
fetchNews();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* broadcastItems()
|
/**
|
||||||
* Broadcast the existing items.
|
* Broadcast the existing items.
|
||||||
*/
|
*/
|
||||||
this.broadcastItems = function () {
|
this.broadcastItems = function () {
|
||||||
@ -148,4 +156,4 @@ var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Fetcher;
|
module.exports = NewsfeedFetcher;
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
const NodeHelper = require("node_helper");
|
const NodeHelper = require("node_helper");
|
||||||
const validUrl = require("valid-url");
|
const validUrl = require("valid-url");
|
||||||
const Fetcher = require("./fetcher.js");
|
const NewsfeedFetcher = require("./newsfeedfetcher.js");
|
||||||
const Log = require("../../../js/logger");
|
const Log = require("../../../js/logger");
|
||||||
|
|
||||||
module.exports = NodeHelper.create({
|
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.
|
* Creates a fetcher for a new feed if it doesn't exist yet.
|
||||||
* Otherwise it reuses the existing one.
|
* Otherwise it reuses the existing one.
|
||||||
*
|
*
|
||||||
* attribute feed object - A feed object.
|
* @param {object} feed The feed object.
|
||||||
* attribute config object - A configuration object containing reload interval in milliseconds.
|
* @param {object} config The configuration object.
|
||||||
*/
|
*/
|
||||||
createFetcher: function (feed, config) {
|
createFetcher: function (feed, config) {
|
||||||
var self = this;
|
const url = feed.url || "";
|
||||||
|
const encoding = feed.encoding || "UTF-8";
|
||||||
var url = feed.url || "";
|
const reloadInterval = feed.reloadInterval || config.reloadInterval || 5 * 60 * 1000;
|
||||||
var encoding = feed.encoding || "UTF-8";
|
|
||||||
var reloadInterval = feed.reloadInterval || config.reloadInterval || 5 * 60 * 1000;
|
|
||||||
|
|
||||||
if (!validUrl.isUri(url)) {
|
if (!validUrl.isUri(url)) {
|
||||||
self.sendSocketNotification("INCORRECT_URL", url);
|
this.sendSocketNotification("INCORRECT_URL", url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fetcher;
|
let fetcher;
|
||||||
if (typeof self.fetchers[url] === "undefined") {
|
if (typeof this.fetchers[url] === "undefined") {
|
||||||
Log.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval);
|
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) {
|
fetcher.onReceive(() => {
|
||||||
self.broadcastFeeds();
|
this.broadcastFeeds();
|
||||||
});
|
});
|
||||||
|
|
||||||
fetcher.onError(function (fetcher, error) {
|
fetcher.onError((fetcher, error) => {
|
||||||
self.sendSocketNotification("FETCH_ERROR", {
|
this.sendSocketNotification("FETCH_ERROR", {
|
||||||
url: fetcher.url(),
|
url: fetcher.url(),
|
||||||
error: error
|
error: error
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
self.fetchers[url] = fetcher;
|
this.fetchers[url] = fetcher;
|
||||||
} else {
|
} else {
|
||||||
Log.log("Use existing news fetcher for url: " + url);
|
Log.log("Use existing news fetcher for url: " + url);
|
||||||
fetcher = self.fetchers[url];
|
fetcher = this.fetchers[url];
|
||||||
fetcher.setReloadInterval(reloadInterval);
|
fetcher.setReloadInterval(reloadInterval);
|
||||||
fetcher.broadcastItems();
|
fetcher.broadcastItems();
|
||||||
}
|
}
|
||||||
@ -70,7 +68,7 @@ module.exports = NodeHelper.create({
|
|||||||
fetcher.startFetch();
|
fetcher.startFetch();
|
||||||
},
|
},
|
||||||
|
|
||||||
/* broadcastFeeds()
|
/**
|
||||||
* Creates an object with all feed items of the different registered feeds,
|
* Creates an object with all feed items of the different registered feeds,
|
||||||
* and broadcasts these using sendSocketNotification.
|
* and broadcasts these using sendSocketNotification.
|
||||||
*/
|
*/
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"configuration_changed": "Die Konfigurationsoptionen für das Newsfeed-Modul haben sich geändert. \nBitte überprüfen Sie die Dokumentation."
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"configuration_changed": "The configuration options for the newsfeed module have changed.\nPlease check the documentation."
|
|
||||||
}
|
|
@ -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."
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"configuration_changed": "Les options de configuration du module newsfeed ont changé. \nVeuillez consulter la documentation."
|
|
||||||
}
|
|
@ -72,7 +72,7 @@ module.exports = NodeHelper.create({
|
|||||||
performFetch: function () {
|
performFetch: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
simpleGits.forEach((sg) => {
|
simpleGits.forEach((sg) => {
|
||||||
sg.git.fetch().status((err, data) => {
|
sg.git.fetch(["--dry-run"]).status((err, data) => {
|
||||||
data.module = sg.module;
|
data.module = sg.module;
|
||||||
if (!err) {
|
if (!err) {
|
||||||
sg.git.log({ "-1": null }, (err, data2) => {
|
sg.git.log({ "-1": null }, (err, data2) => {
|
||||||
|
@ -3,76 +3,155 @@
|
|||||||
/* Magic Mirror
|
/* Magic Mirror
|
||||||
* Module: Weather
|
* Module: Weather
|
||||||
* Provider: weather.gov
|
* Provider: weather.gov
|
||||||
|
* https://weather-gov.github.io/api/general-faqs
|
||||||
*
|
*
|
||||||
* By Vince Peri
|
* Original by Vince Peri
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
*
|
*
|
||||||
* This class is a provider for weather.gov.
|
* 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
|
* 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", {
|
WeatherProvider.register("weathergov", {
|
||||||
// Set the name of the provider.
|
// Set the name of the provider.
|
||||||
// This isn't strictly necessary, since it will fallback to the provider identifier
|
// 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.
|
// But for debugging (and future alerts) it would be nice to have the real name.
|
||||||
providerName: "Weather.gov",
|
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.
|
// Overwrite the fetchCurrentWeather method.
|
||||||
fetchCurrentWeather() {
|
fetchCurrentWeather() {
|
||||||
this.fetchData(this.getUrl())
|
if (!this.configURLs) {
|
||||||
|
Log.info("fetch wx waiting on config URLs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fetchData(this.stationObsURL)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
|
if (!data || !data.properties) {
|
||||||
// Did not receive usable new data.
|
// Did not receive usable new data.
|
||||||
// Maybe this needs a better check?
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties);
|
||||||
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties.periods[0]);
|
|
||||||
this.setCurrentWeather(currentWeather);
|
this.setCurrentWeather(currentWeather);
|
||||||
})
|
})
|
||||||
.catch(function (request) {
|
.catch(function (request) {
|
||||||
Log.error("Could not load data ... ", request);
|
Log.error("Could not load station obs data ... ", request);
|
||||||
})
|
})
|
||||||
.finally(() => this.updateAvailable());
|
.finally(() => this.updateAvailable());
|
||||||
},
|
},
|
||||||
|
|
||||||
// Overwrite the fetchCurrentWeather method.
|
// Overwrite the fetchWeatherForecast method.
|
||||||
fetchWeatherForecast() {
|
fetchWeatherForecast() {
|
||||||
this.fetchData(this.getUrl())
|
if (!this.configURLs) {
|
||||||
|
Log.info("fetch wx waiting on config URLs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fetchData(this.forecastURL)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
|
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
|
||||||
// Did not receive usable new data.
|
// Did not receive usable new data.
|
||||||
// Maybe this needs a better check?
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods);
|
const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods);
|
||||||
this.setWeatherForecast(forecast);
|
this.setWeatherForecast(forecast);
|
||||||
})
|
})
|
||||||
.catch(function (request) {
|
.catch(function (request) {
|
||||||
Log.error("Could not load data ... ", request);
|
Log.error("Could not load forecast hourly data ... ", request);
|
||||||
})
|
})
|
||||||
.finally(() => this.updateAvailable());
|
.finally(() => this.updateAvailable());
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Weather.gov Specific Methods - These are not part of the default provider methods */
|
/** Weather.gov Specific Methods - These are not part of the default provider methods */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Gets the complete url for the request
|
* Get specific URLs
|
||||||
*/
|
*/
|
||||||
getUrl() {
|
fetchWxGovURLs(config) {
|
||||||
return this.config.apiBase + this.config.lat + "," + this.config.lon + this.config.weatherEndpoint;
|
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
|
* 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) {
|
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
currentWeather.temperature = currentWeatherData.temperature;
|
currentWeather.date = moment(currentWeatherData.timestamp);
|
||||||
currentWeather.windSpeed = currentWeatherData.windSpeed.split(" ", 1);
|
currentWeather.temperature = this.convertTemp(currentWeatherData.temperature.value);
|
||||||
currentWeather.windDirection = this.convertWindDirection(currentWeatherData.windDirection);
|
currentWeather.windSpeed = this.covertSpeed(currentWeatherData.windSpeed.value);
|
||||||
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.shortForecast, currentWeatherData.isDaytime);
|
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
|
// determine the sunrise/sunset times - not supplied in weather.gov data
|
||||||
let times = this.calcAstroData(this.config.lat, this.config.lon);
|
let times = this.calcAstroData(this.config.lat, this.config.lon);
|
||||||
@ -124,7 +203,7 @@ WeatherProvider.register("weathergov", {
|
|||||||
// specify date
|
// specify date
|
||||||
weather.date = moment(forecast.startTime);
|
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);
|
weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +227,34 @@ WeatherProvider.register("weathergov", {
|
|||||||
return days.slice(1);
|
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
|
* Calculate the astronomical data
|
||||||
*/
|
*/
|
||||||
|
@ -15,7 +15,6 @@ Module.register("weather", {
|
|||||||
|
|
||||||
location: false,
|
location: false,
|
||||||
locationID: false,
|
locationID: false,
|
||||||
appid: "",
|
|
||||||
units: config.units,
|
units: config.units,
|
||||||
|
|
||||||
tempUnits: config.units,
|
tempUnits: config.units,
|
||||||
@ -43,8 +42,10 @@ Module.register("weather", {
|
|||||||
initialLoadDelay: 0, // 0 seconds delay
|
initialLoadDelay: 0, // 0 seconds delay
|
||||||
retryDelay: 2500,
|
retryDelay: 2500,
|
||||||
|
|
||||||
|
apiKey: "",
|
||||||
|
apiSecret: "",
|
||||||
apiVersion: "2.5",
|
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",
|
weatherEndpoint: "/weather",
|
||||||
|
|
||||||
appendLocationNameToHeader: true,
|
appendLocationNameToHeader: true,
|
||||||
|
@ -12,7 +12,7 @@ var WeatherProvider = Class.extend({
|
|||||||
// Weather Provider Properties
|
// Weather Provider Properties
|
||||||
providerName: null,
|
providerName: null,
|
||||||
|
|
||||||
// The following properties have accestor methods.
|
// The following properties have accessor methods.
|
||||||
// Try to not access them directly.
|
// Try to not access them directly.
|
||||||
currentWeatherObject: null,
|
currentWeatherObject: null,
|
||||||
weatherForecastArray: null,
|
weatherForecastArray: null,
|
||||||
@ -119,6 +119,9 @@ WeatherProvider.providers = [];
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Static method to register a new weather provider.
|
* Static method to register a new weather provider.
|
||||||
|
*
|
||||||
|
* @param {string} providerIdentifier The name of the weather provider
|
||||||
|
* @param {object} providerDetails The details of the weather provider
|
||||||
*/
|
*/
|
||||||
WeatherProvider.register = function (providerIdentifier, providerDetails) {
|
WeatherProvider.register = function (providerIdentifier, providerDetails) {
|
||||||
WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails);
|
WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails);
|
||||||
@ -126,6 +129,10 @@ WeatherProvider.register = function (providerIdentifier, providerDetails) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Static method to initialize a new weather provider.
|
* Static method to initialize a new weather provider.
|
||||||
|
*
|
||||||
|
* @param {string} providerIdentifier The name of the weather provider
|
||||||
|
* @param {object} delegate The weather module
|
||||||
|
* @returns {object} The new weather provider
|
||||||
*/
|
*/
|
||||||
WeatherProvider.initialize = function (providerIdentifier, delegate) {
|
WeatherProvider.initialize = function (providerIdentifier, delegate) {
|
||||||
providerIdentifier = providerIdentifier.toLowerCase();
|
providerIdentifier = providerIdentifier.toLowerCase();
|
||||||
|
@ -9,6 +9,8 @@ Module.register("weatherforecast", {
|
|||||||
defaults: {
|
defaults: {
|
||||||
location: false,
|
location: false,
|
||||||
locationID: false,
|
locationID: false,
|
||||||
|
lat: false,
|
||||||
|
lon: false,
|
||||||
appid: "",
|
appid: "",
|
||||||
units: config.units,
|
units: config.units,
|
||||||
maxNumberOfDays: 7,
|
maxNumberOfDays: 7,
|
||||||
@ -29,6 +31,7 @@ Module.register("weatherforecast", {
|
|||||||
apiVersion: "2.5",
|
apiVersion: "2.5",
|
||||||
apiBase: "https://api.openweathermap.org/data/",
|
apiBase: "https://api.openweathermap.org/data/",
|
||||||
forecastEndpoint: "forecast/daily",
|
forecastEndpoint: "forecast/daily",
|
||||||
|
excludes: false,
|
||||||
|
|
||||||
appendLocationNameToHeader: true,
|
appendLocationNameToHeader: true,
|
||||||
calendarClass: "calendar",
|
calendarClass: "calendar",
|
||||||
@ -283,6 +286,8 @@ Module.register("weatherforecast", {
|
|||||||
var params = "?";
|
var params = "?";
|
||||||
if (this.config.locationID) {
|
if (this.config.locationID) {
|
||||||
params += "id=" + 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) {
|
} else if (this.config.location) {
|
||||||
params += "q=" + this.config.location;
|
params += "q=" + this.config.location;
|
||||||
} else if (this.firstEvent && this.firstEvent.geo) {
|
} else if (this.firstEvent && this.firstEvent.geo) {
|
||||||
@ -294,8 +299,17 @@ Module.register("weatherforecast", {
|
|||||||
return;
|
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 += "&units=" + this.config.units;
|
||||||
params += "&lang=" + this.config.lang;
|
params += "&lang=" + this.config.lang;
|
||||||
params += "&APPID=" + this.config.appid;
|
params += "&APPID=" + this.config.appid;
|
||||||
@ -323,15 +337,34 @@ Module.register("weatherforecast", {
|
|||||||
* argument data object - Weather information received form openweather.org.
|
* argument data object - Weather information received form openweather.org.
|
||||||
*/
|
*/
|
||||||
processWeather: function (data) {
|
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 = [];
|
this.forecast = [];
|
||||||
var lastDay = null;
|
var lastDay = null;
|
||||||
var forecastData = {};
|
var forecastData = {};
|
||||||
|
|
||||||
for (var i = 0, count = data.list.length; i < count; i++) {
|
// Handle different structs between forecast16 and onecall endpoints
|
||||||
var forecast = data.list[i];
|
var forecastList = null;
|
||||||
this.parserDataWeather(forecast); // hack issue #1017
|
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 day;
|
||||||
var hour;
|
var hour;
|
||||||
@ -349,7 +382,7 @@ Module.register("weatherforecast", {
|
|||||||
icon: this.config.iconTable[forecast.weather[0].icon],
|
icon: this.config.iconTable[forecast.weather[0].icon],
|
||||||
maxTemp: this.roundValue(forecast.temp.max),
|
maxTemp: this.roundValue(forecast.temp.max),
|
||||||
minTemp: this.roundValue(forecast.temp.min),
|
minTemp: this.roundValue(forecast.temp.min),
|
||||||
rain: this.processRain(forecast, data.list)
|
rain: this.processRain(forecast, forecastList)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.forecast.push(forecastData);
|
this.forecast.push(forecastData);
|
||||||
|
1041
package-lock.json
generated
19
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "magicmirror",
|
"name": "magicmirror",
|
||||||
"version": "2.12.0",
|
"version": "2.13.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": {
|
||||||
@ -9,9 +9,10 @@
|
|||||||
"install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error",
|
"install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error",
|
||||||
"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 ./node_modules/mocha/bin/mocha tests --recursive",
|
"test": "NODE_ENV=test mocha tests --recursive",
|
||||||
"test:unit": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/unit --recursive",
|
"test:coverage": "NODE_ENV=test nyc mocha tests --recursive --timeout=3000",
|
||||||
"test:e2e": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/e2e --recursive",
|
"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: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/**/*.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",
|
||||||
@ -47,6 +48,7 @@
|
|||||||
"current-week-number": "^1.0.7",
|
"current-week-number": "^1.0.7",
|
||||||
"danger": "^3.1.3",
|
"danger": "^3.1.3",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.11.0",
|
||||||
|
"eslint-plugin-jsdoc": "^30.1.0",
|
||||||
"eslint-plugin-prettier": "^3.1.4",
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
"http-auth": "^3.2.3",
|
"http-auth": "^3.2.3",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.2.5",
|
||||||
@ -54,6 +56,7 @@
|
|||||||
"mocha": "^7.1.2",
|
"mocha": "^7.1.2",
|
||||||
"mocha-each": "^2.0.1",
|
"mocha-each": "^2.0.1",
|
||||||
"mocha-logger": "^1.0.6",
|
"mocha-logger": "^1.0.6",
|
||||||
|
"nyc": "^15.1.0",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.0.5",
|
||||||
"pretty-quick": "^2.0.1",
|
"pretty-quick": "^2.0.1",
|
||||||
"spectron": "^8.0.0",
|
"spectron": "^8.0.0",
|
||||||
@ -68,18 +71,18 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"console-stamp": "^0.2.9",
|
"console-stamp": "^0.2.9",
|
||||||
"eslint": "^7.3.0",
|
"eslint": "^7.5.0",
|
||||||
"express": "^4.16.2",
|
"express": "^4.16.2",
|
||||||
"express-ipfilter": "^1.0.1",
|
"express-ipfilter": "^1.0.1",
|
||||||
"feedme": "latest",
|
"feedme": "latest",
|
||||||
"helmet": "^3.21.2",
|
"helmet": "^3.23.3",
|
||||||
"ical": "^0.8.0",
|
"ical": "^0.8.0",
|
||||||
"iconv-lite": "latest",
|
"iconv-lite": "latest",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.19",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"moment": "latest",
|
"moment": "latest",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"rrule": "^2.6.2",
|
"rrule": "^2.6.4",
|
||||||
"rrule-alt": "^2.2.8",
|
"rrule-alt": "^2.2.8",
|
||||||
"simple-git": "^1.85.0",
|
"simple-git": "^1.85.0",
|
||||||
"socket.io": "^2.1.1",
|
"socket.io": "^2.1.1",
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
// Escaped
|
|
||||||
"FOO\"BAR": "Today",
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The following lines
|
|
||||||
* represent cardinal directions
|
|
||||||
*/
|
|
||||||
"N": "N",
|
|
||||||
"E": "E",
|
|
||||||
"S": "S",
|
|
||||||
"W": "W"
|
|
||||||
}
|
|
56
tests/configs/data/calendar_test_icons.ics
Normal file
@ -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
|
41
tests/configs/modules/calendar/custom.js
Normal file
@ -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;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
const helpers = require("../global-setup");
|
const helpers = require("../global-setup");
|
||||||
const serverBasicAuth = require("../../servers/basic-auth.js");
|
const serverBasicAuth = require("../../servers/basic-auth.js");
|
||||||
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
const it = global.it;
|
||||||
@ -31,8 +32,47 @@ describe("Calendar module", function () {
|
|||||||
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/default.js";
|
process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/default.js";
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return TestEvents", function () {
|
it("should show the default maximumEntries of 10", async () => {
|
||||||
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
|
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());
|
serverBasicAuth.close(done());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return TestEvents", function () {
|
it("should return TestEvents", function () {
|
||||||
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
|
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -63,7 +103,7 @@ describe("Calendar module", function () {
|
|||||||
serverBasicAuth.close(done());
|
serverBasicAuth.close(done());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return TestEvents", function () {
|
it("should return TestEvents", function () {
|
||||||
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
|
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -79,7 +119,7 @@ describe("Calendar module", function () {
|
|||||||
serverBasicAuth.close(done());
|
serverBasicAuth.close(done());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return TestEvents", function () {
|
it("should return TestEvents", function () {
|
||||||
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
|
return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -95,7 +135,7 @@ describe("Calendar module", function () {
|
|||||||
serverBasicAuth.close(done());
|
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);
|
return app.client.waitUntilTextExists(".calendar", "No upcoming events.", 10000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
var path = require("path");
|
const path = require("path");
|
||||||
var auth = require("http-auth");
|
const auth = require("http-auth");
|
||||||
var express = require("express");
|
const express = require("express");
|
||||||
var app = express();
|
const app = express();
|
||||||
|
|
||||||
var server;
|
var server;
|
||||||
|
|
||||||
|
@ -171,25 +171,6 @@ describe("Translator", function () {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should strip comments", function (done) {
|
|
||||||
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
|
|
||||||
dom.window.onload = function () {
|
|
||||||
const { Translator } = dom.window;
|
|
||||||
const file = "StripComments.json";
|
|
||||||
|
|
||||||
Translator.load(mmm, file, false, function () {
|
|
||||||
expect(Translator.translations[mmm.name]).to.be.deep.equal({
|
|
||||||
'FOO"BAR': "Today",
|
|
||||||
N: "N",
|
|
||||||
E: "E",
|
|
||||||
S: "S",
|
|
||||||
W: "W"
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not load translations, if module fallback exists", function (done) {
|
it("should not load translations, if module fallback exists", function (done) {
|
||||||
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
|
const dom = new JSDOM(`<script>var Log = {log: function(){}};</script><script src="${path.join(__dirname, "..", "..", "..", "js", "translator.js")}">`, { runScripts: "dangerously", resources: "usable" });
|
||||||
dom.window.onload = function () {
|
dom.window.onload = function () {
|
||||||
@ -205,7 +186,7 @@ describe("Translator", function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Translator.load(mmm, file, false, function () {
|
Translator.load(mmm, file, false, function () {
|
||||||
expect(Translator.translations[mmm.name]).to.be.undefined;
|
expect(Translator.translations[mmm.name]).to.be.equal(undefined);
|
||||||
expect(Translator.translationsFallback[mmm.name]).to.be.deep.equal({
|
expect(Translator.translationsFallback[mmm.name]).to.be.deep.equal({
|
||||||
Hello: "Hallo"
|
Hello: "Hallo"
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
var expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
var Utils = require("../../../js/utils.js");
|
const Utils = require("../../../js/utils.js");
|
||||||
var colors = require("colors/safe");
|
const colors = require("colors/safe");
|
||||||
|
|
||||||
describe("Utils", function () {
|
describe("Utils", function () {
|
||||||
describe("colors", function () {
|
describe("colors", function () {
|
||||||
|
@ -32,54 +32,54 @@ describe("Functions into modules/default/calendar/calendar.js", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("getLocaleSpecification", function () {
|
describe("getLocaleSpecification", function () {
|
||||||
it("Should return a valid moment.LocaleSpecification for a 12-hour format", function () {
|
it("should return a valid moment.LocaleSpecification for a 12-hour format", function () {
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification(12)).to.deep.equal({ longDateFormat: { LT: "h:mm A" } });
|
expect(Module.definitions.calendar.getLocaleSpecification(12)).to.deep.equal({ longDateFormat: { LT: "h:mm A" } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return a valid moment.LocaleSpecification for a 24-hour format", function () {
|
it("should return a valid moment.LocaleSpecification for a 24-hour format", function () {
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification(24)).to.deep.equal({ longDateFormat: { LT: "HH:mm" } });
|
expect(Module.definitions.calendar.getLocaleSpecification(24)).to.deep.equal({ longDateFormat: { LT: "HH:mm" } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return the current system locale when called without timeFormat number", function () {
|
it("should return the current system locale when called without timeFormat number", function () {
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: moment.localeData().longDateFormat("LT") } });
|
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: moment.localeData().longDateFormat("LT") } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return a 12-hour longDateFormat when using the 'en' locale", function () {
|
it("should return a 12-hour longDateFormat when using the 'en' locale", function () {
|
||||||
var localeBackup = moment.locale();
|
var localeBackup = moment.locale();
|
||||||
moment.locale("en");
|
moment.locale("en");
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "h:mm A" } });
|
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "h:mm A" } });
|
||||||
moment.locale(localeBackup);
|
moment.locale(localeBackup);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return a 12-hour longDateFormat when using the 'au' locale", function () {
|
it("should return a 12-hour longDateFormat when using the 'au' locale", function () {
|
||||||
var localeBackup = moment.locale();
|
var localeBackup = moment.locale();
|
||||||
moment.locale("au");
|
moment.locale("au");
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "h:mm A" } });
|
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "h:mm A" } });
|
||||||
moment.locale(localeBackup);
|
moment.locale(localeBackup);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return a 12-hour longDateFormat when using the 'eg' locale", function () {
|
it("should return a 12-hour longDateFormat when using the 'eg' locale", function () {
|
||||||
var localeBackup = moment.locale();
|
var localeBackup = moment.locale();
|
||||||
moment.locale("eg");
|
moment.locale("eg");
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "h:mm A" } });
|
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "h:mm A" } });
|
||||||
moment.locale(localeBackup);
|
moment.locale(localeBackup);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return a 24-hour longDateFormat when using the 'nl' locale", function () {
|
it("should return a 24-hour longDateFormat when using the 'nl' locale", function () {
|
||||||
var localeBackup = moment.locale();
|
var localeBackup = moment.locale();
|
||||||
moment.locale("nl");
|
moment.locale("nl");
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "HH:mm" } });
|
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "HH:mm" } });
|
||||||
moment.locale(localeBackup);
|
moment.locale(localeBackup);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return a 24-hour longDateFormat when using the 'fr' locale", function () {
|
it("should return a 24-hour longDateFormat when using the 'fr' locale", function () {
|
||||||
var localeBackup = moment.locale();
|
var localeBackup = moment.locale();
|
||||||
moment.locale("fr");
|
moment.locale("fr");
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "HH:mm" } });
|
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "HH:mm" } });
|
||||||
moment.locale(localeBackup);
|
moment.locale(localeBackup);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return a 24-hour longDateFormat when using the 'uk' locale", function () {
|
it("should return a 24-hour longDateFormat when using the 'uk' locale", function () {
|
||||||
var localeBackup = moment.locale();
|
var localeBackup = moment.locale();
|
||||||
moment.locale("uk");
|
moment.locale("uk");
|
||||||
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "HH:mm" } });
|
expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: { LT: "HH:mm" } });
|
||||||
@ -120,5 +120,11 @@ describe("Functions into modules/default/calendar/calendar.js", function () {
|
|||||||
"This is a wrapEvent <br>test. Should wrap the string <br>instead of shorten it if called <br>with wrapEvent = true"
|
"This is a wrapEvent <br>test. Should wrap the string <br>instead of shorten it if called <br>with wrapEvent = true"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should wrap and shorten the string in the second line if called with wrapEvents = true and maxTitleLines = 2", function () {
|
||||||
|
expect(Module.definitions.calendar.shorten("This is a wrapEvent and maxTitleLines test. Should wrap and shorten the string in the second line if called with wrapEvents = true and maxTitleLines = 2", undefined, true, 2)).to.equal(
|
||||||
|
"This is a wrapEvent and <br>maxTitleLines test. Should wrap and …"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* eslint no-multi-spaces: 0 */
|
/* eslint no-multi-spaces: 0 */
|
||||||
var expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
describe("Functions module currentweather", function () {
|
describe("Functions module currentweather", function () {
|
||||||
// Fake for use by currentweather.js
|
// Fake for use by currentweather.js
|
||||||
|
@ -1,29 +1,15 @@
|
|||||||
var expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
describe("Functions into modules/default/newsfeed/newsfeed.js", function () {
|
describe("Functions into modules/default/newsfeed/newsfeed.js", function () {
|
||||||
|
// Fake for use by newsletter.js
|
||||||
Module = {};
|
Module = {};
|
||||||
Module.definitions = {};
|
Module.definitions = {};
|
||||||
Module.register = function (name, moduleDefinition) {
|
Module.register = function (name, moduleDefinition) {
|
||||||
Module.definitions[name] = moduleDefinition;
|
Module.definitions[name] = moduleDefinition;
|
||||||
};
|
};
|
||||||
|
|
||||||
// load newsfeed.js
|
before(function () {
|
||||||
require("../../../modules/default/newsfeed/newsfeed.js");
|
// load newsfeed.js
|
||||||
|
require("../../../modules/default/newsfeed/newsfeed.js");
|
||||||
describe("capitalizeFirstLetter", function () {
|
|
||||||
const words = {
|
|
||||||
rodrigo: "Rodrigo",
|
|
||||||
"123m": "123m",
|
|
||||||
"magic mirror": "Magic mirror",
|
|
||||||
",a": ",a",
|
|
||||||
ñandú: "Ñandú",
|
|
||||||
".!": ".!"
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(words).forEach((word) => {
|
|
||||||
it(`for ${word} should return ${words[word]}`, function () {
|
|
||||||
expect(Module.definitions.newsfeed.capitalizeFirstLetter(word)).to.equal(words[word]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* eslint no-multi-spaces: 0 */
|
/* eslint no-multi-spaces: 0 */
|
||||||
var expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
describe("Functions module weatherforecast", function () {
|
describe("Functions module weatherforecast", function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
var fs = require("fs");
|
const fs = require("fs");
|
||||||
var path = require("path");
|
const path = require("path");
|
||||||
var expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
var vm = require("vm");
|
const vm = require("vm");
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
var basedir = path.join(__dirname, "../../..");
|
var basedir = path.join(__dirname, "../../..");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
var fs = require("fs");
|
const fs = require("fs");
|
||||||
var path = require("path");
|
const path = require("path");
|
||||||
var expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
var vm = require("vm");
|
const vm = require("vm");
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
var basedir = path.join(__dirname, "../../..");
|
var basedir = path.join(__dirname, "../../..");
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
"NW": "NW",
|
"NW": "NW",
|
||||||
"NNW": "NNW",
|
"NNW": "NNW",
|
||||||
|
|
||||||
|
"MODULE_CONFIG_CHANGED": "Die Konfigurationsoptionen für das {MODULE_NAME} Modul haben sich geändert. \nBitte überprüfen Sie die Dokumentation.",
|
||||||
|
|
||||||
"UPDATE_NOTIFICATION": "Aktualisierung für MagicMirror² verfügbar.",
|
"UPDATE_NOTIFICATION": "Aktualisierung für MagicMirror² verfügbar.",
|
||||||
"UPDATE_NOTIFICATION_MODULE": "Aktualisierung für das {MODULE_NAME} Modul verfügbar.",
|
"UPDATE_NOTIFICATION_MODULE": "Aktualisierung für das {MODULE_NAME} Modul verfügbar.",
|
||||||
"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.",
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
"NW": "NW",
|
"NW": "NW",
|
||||||
"NNW": "NNW",
|
"NNW": "NNW",
|
||||||
|
|
||||||
|
"MODULE_CONFIG_CHANGED": "The configuration options for the {MODULE_NAME} module have changed.\nPlease check the documentation.",
|
||||||
|
|
||||||
"UPDATE_NOTIFICATION": "MagicMirror² update available.",
|
"UPDATE_NOTIFICATION": "MagicMirror² update available.",
|
||||||
"UPDATE_NOTIFICATION_MODULE": "Update available for {MODULE_NAME} module.",
|
"UPDATE_NOTIFICATION_MODULE": "Update available for {MODULE_NAME} module.",
|
||||||
"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.",
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
"NW": "NO",
|
"NW": "NO",
|
||||||
"NNW": "NNO",
|
"NNW": "NNO",
|
||||||
|
|
||||||
|
"MODULE_CONFIG_CHANGED": "Las opciones de configuración para el módulo {MODULE_NAME} han cambiado. \nVerifique la documentación.",
|
||||||
|
|
||||||
"UPDATE_NOTIFICATION": "MagicMirror² actualización disponible.",
|
"UPDATE_NOTIFICATION": "MagicMirror² actualización disponible.",
|
||||||
"UPDATE_NOTIFICATION_MODULE": "Disponible una actualización para el módulo {MODULE_NAME}.",
|
"UPDATE_NOTIFICATION_MODULE": "Disponible una actualización para el módulo {MODULE_NAME}.",
|
||||||
"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}.",
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
"NW": "NO",
|
"NW": "NO",
|
||||||
"NNW": "NNO",
|
"NNW": "NNO",
|
||||||
|
|
||||||
|
"MODULE_CONFIG_CHANGED": "Les options de configuration du module {MODULE_NAME} ont changé. \nVeuillez consulter la documentation.",
|
||||||
|
|
||||||
"UPDATE_NOTIFICATION": "Une mise à jour de MagicMirror² est disponible",
|
"UPDATE_NOTIFICATION": "Une mise à jour de MagicMirror² est disponible",
|
||||||
"UPDATE_NOTIFICATION_MODULE": "Une mise à jour est disponible pour le module {MODULE_NAME} .",
|
"UPDATE_NOTIFICATION_MODULE": "Une mise à jour est disponible pour le module {MODULE_NAME} .",
|
||||||
"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} .",
|
||||||
|
36
translations/lt.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"LOADING": "Kraunasi …",
|
||||||
|
|
||||||
|
"TODAY": "Šiandien",
|
||||||
|
"TOMORROW": "Rytoj",
|
||||||
|
"DAYAFTERTOMORROW": "Už 2 dienų",
|
||||||
|
"RUNNING": "Pasibaigs už",
|
||||||
|
"EMPTY": "Nėra artimų įvykių.",
|
||||||
|
|
||||||
|
"WEEK": "{weekNumber} savaitė",
|
||||||
|
|
||||||
|
"N": "N",
|
||||||
|
"NNE": "NNE",
|
||||||
|
"NE": "NE",
|
||||||
|
"ENE": "ENE",
|
||||||
|
"E": "E",
|
||||||
|
"ESE": "ESE",
|
||||||
|
"SE": "SE",
|
||||||
|
"SSE": "SSE",
|
||||||
|
"S": "S",
|
||||||
|
"SSW": "SSW",
|
||||||
|
"SW": "SW",
|
||||||
|
"WSW": "WSW",
|
||||||
|
"W": "W",
|
||||||
|
"WNW": "WNW",
|
||||||
|
"NW": "NW",
|
||||||
|
"NNW": "NNW",
|
||||||
|
|
||||||
|
"UPDATE_NOTIFICATION": "Galimas MagicMirror² naujinimas.",
|
||||||
|
"UPDATE_NOTIFICATION_MODULE": "Galimas {MODULE_NAME} naujinimas.",
|
||||||
|
"UPDATE_INFO_SINGLE": "Šis įdiegimas atsilieka {COMMIT_COUNT} įsipareigojimu {BRANCH_NAME} šakoje.",
|
||||||
|
"UPDATE_INFO_MULTIPLE": "Šis įdiegimas atsilieka {COMMIT_COUNT} įsipareigojimais {BRANCH_NAME} šakoje.",
|
||||||
|
|
||||||
|
"FEELS": "Jaučiasi kaip",
|
||||||
|
"PRECIP": "Krituliai"
|
||||||
|
}
|
1140
vendor/package-lock.json
generated
vendored
8
vendor/package.json
vendored
@ -10,10 +10,10 @@
|
|||||||
"url": "https://github.com/MichMich/MagicMirror/issues"
|
"url": "https://github.com/MichMich/MagicMirror/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.3.1",
|
"@fortawesome/fontawesome-free": "^5.13.1",
|
||||||
"moment": "^2.17.1",
|
"moment": "^2.27.0",
|
||||||
"moment-timezone": "^0.5.11",
|
"moment-timezone": "^0.5.31",
|
||||||
"nunjucks": "^3.0.1",
|
"nunjucks": "^3.2.1",
|
||||||
"suncalc": "^1.8.0",
|
"suncalc": "^1.8.0",
|
||||||
"weathericons": "^2.1.0"
|
"weathericons": "^2.1.0"
|
||||||
}
|
}
|
||||||
|