diff --git a/.github/codecov.yml b/.github/codecov.yml
new file mode 100644
index 00000000..3f76e9b5
--- /dev/null
+++ b/.github/codecov.yml
@@ -0,0 +1,6 @@
+coverage:
+ status:
+ project:
+ default:
+ # advanced settings
+ informational: true
diff --git a/.github/workflows/node-ci.js.yml b/.github/workflows/node-ci.js.yml
index 7f6f23b1..3634a728 100644
--- a/.github/workflows/node-ci.js.yml
+++ b/.github/workflows/node-ci.js.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [10.x, 12.x, 14.x]
+ node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
diff --git a/.husky/.gitignore b/.husky/.gitignore
new file mode 100644
index 00000000..31354ec1
--- /dev/null
+++ b/.husky/.gitignore
@@ -0,0 +1 @@
+_
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 00000000..3199e8e0
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npm run lint:staged
diff --git a/.prettierignore b/.prettierignore
index b829fa7c..eeaa248d 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,5 +1,8 @@
-package-lock.json
-/config/**/*
-/vendor/**/*
+/config
+/coverage
+/vendor
!/vendor/vendor.js
-.github/**/*
+.github
+.nyc_output
+package-lock.json
+*.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b08ab4c9..f6e5df9a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,51 @@ This project adheres to [Semantic Versioning](https://semver.org/).
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
+## [2.16.0] - Unreleased (Develop Branch)
+
+_This release is scheduled to be released on 2021-07-01._
+
+Special thanks to the following contributors: @B1gG, @codac, @ezeholz, @khassel, @KristjanESPERANTO, @rejas, @earlman, Faizan Ahmed.
+
+### Added
+
+- Added French translations for "MODULE_CONFIG_ERROR" and "PRECIP".
+- Added German translation for "PRECIP".
+- Added first test for Alert module.
+- Added support for `dateFormat` when not using `timeFormat: "absolute"`
+- Added custom-properties for colors and fonts for improved styling experience, see `custom.css.sample` file
+- Added custom-properties for gaps around body and between modules
+- Added test case for recurring calendar events
+- Added new Environment Canada provider for default WEATHER module (weather data for Canadian locations only)
+
+### Updated
+
+- Bump node-ical to v0.13.0 (now last runtime dependency using deprecated `request` package is removed).
+- Use codecov in informational mode
+- Refactor code into es6 where possible (e.g. var -> let/const)
+- Use node v16 in github workflow (replacing node v10)
+- Moved some files into better suited directories
+- Update dependencies in package.json, require node >= v12, remove `rrule-alt` and `rrule`
+- Update dependencies in package.json and migrate husky to v6, fix husky setup in prod environment
+- Cleaned up error handling in newsfeed and calendar modules for real
+
+### Removed
+
+### Fixed
+
+- Fix calendar start function logging inconsistency.
+- Fix updatenotification start function logging inconsistency.
+- Checks and applies the showDescription setting for the newsfeed module again
+- Fix tests in weather module and add one for decimalPoint in forecast
+- Fix decimalSymbol in the forecast part of the new weather module #2530
+- Fix wrong treatment of `appendLocationNameToHeader` when using `ukmetofficedatahub`
+- Fix alert not recognizing multiple alerts (#2522)
+- Fix fetch option httpsAgent to agent in calendar module (#466)
+- Fix module updatenotification which did not work for repos with many refs (#1907)
+- Fix config check failing when encountering let syntax ("Parsing error: Unexpected token config")
+- Fix calendar debug check
+- Really run prettier over all files
+
## [2.15.0] - 2021-04-01
Special thanks to the following contributors: @EdgardosReis, @MystaraTheGreat, @TheDuffman85, @ashishtank, @buxxi, @codac, @fewieden, @khassel, @klaernie, @qu1que, @rejas, @sdetweil & @thomasrockhu.
diff --git a/README.md b/README.md
index 0d75c7ef..8a243f89 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,6 @@
-
**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](https://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors).
@@ -23,6 +22,7 @@ For the full documentation including **[installation instructions](https://docs.
- Website: [https://magicmirror.builders](https://magicmirror.builders)
- Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders)
- Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders)
+ - Technical discussions: https://forum.magicmirror.builders/category/11/core-system
- Discord: [https://discord.gg/J5BAtvx](https://discord.gg/J5BAtvx)
- Blog: [https://michaelteeuw.nl/tagged/magicmirror](https://michaelteeuw.nl/tagged/magicmirror)
- Donations: [https://magicmirror.builders/#donate](https://magicmirror.builders/#donate)
diff --git a/clientonly/index.js b/clientonly/index.js
index 3564eef8..1a0a7970 100644
--- a/clientonly/index.js
+++ b/clientonly/index.js
@@ -2,7 +2,7 @@
// Use separate scope to prevent global scope pollution
(function () {
- var config = {};
+ const config = {};
/**
* Helper function to get server address/hostname from either the commandline or env
@@ -17,8 +17,8 @@
* @returns {string} the value of the parameter
*/
function getCommandLineParameter(key, defaultValue = undefined) {
- var index = process.argv.indexOf(`--${key}`);
- var value = index > -1 ? process.argv[index + 1] : undefined;
+ const index = process.argv.indexOf(`--${key}`);
+ const value = index > -1 ? process.argv[index + 1] : undefined;
return value !== undefined ? String(value) : defaultValue;
}
@@ -43,7 +43,7 @@
// Select http or https module, depending on requested url
const lib = url.startsWith("https") ? require("https") : require("http");
const request = lib.get(url, (response) => {
- var configData = "";
+ let configData = "";
// Gather incoming data
response.on("data", function (chunk) {
@@ -79,15 +79,15 @@
getServerAddress();
(config.address && config.port) || fail();
- var prefix = config.tls ? "https://" : "http://";
+ const prefix = config.tls ? "https://" : "http://";
// Only start the client if a non-local server was provided
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) {
getServerConfig(`${prefix}${config.address}:${config.port}/config/`)
.then(function (configReturn) {
// Pass along the server config via an environment variable
- var env = Object.create(process.env);
- var options = { env: env };
+ const env = Object.create(process.env);
+ const options = { env: env };
configReturn.address = config.address;
configReturn.port = config.port;
configReturn.tls = config.tls;
diff --git a/config/config.js.sample b/config/config.js.sample
index e17ae195..e221b5ed 100644
--- a/config/config.js.sample
+++ b/config/config.js.sample
@@ -7,8 +7,7 @@
* See https://github.com/MichMich/MagicMirror#configuration
*
*/
-
-var config = {
+let config = {
address: "localhost", // Address to listen on, can be:
// - "localhost", "127.0.0.1", "::1" to listen on loopback interface
// - another specific IPv4/6 to listen on a specific interface
diff --git a/css/custom.css.sample b/css/custom.css.sample
new file mode 100644
index 00000000..ac5b5e2e
--- /dev/null
+++ b/css/custom.css.sample
@@ -0,0 +1,31 @@
+/* Magic Mirror Custom CSS Sample
+ *
+ * Change color and fonts here.
+ *
+ * Beware that properties cannot be unitless, so for example write '--gap-body: 0px;' instead of just '--gap-body: 0;'
+ *
+ * MIT Licensed.
+ */
+
+/* Uncomment and adjust accordingly if you want to import another font from the google-fonts-api: */
+/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;700&display=swap'); */
+
+:root {
+ --color-text: #999;
+ --color-text-dimmed: #666;
+ --color-text-bright: #fff;
+ --color-background: black;
+
+ --font-primary: "Roboto Condensed";
+ --font-secondary: "Roboto";
+
+ --font-size: 20px;
+ --font-size-small: 0.75rem;
+
+ --gap-body-top: 60px;
+ --gap-body-right: 60px;
+ --gap-body-bottom: 60px;
+ --gap-body-left: 60px;
+
+ --gap-modules: 30px;
+}
diff --git a/css/main.css b/css/main.css
index 16c281be..a36ca5e4 100644
--- a/css/main.css
+++ b/css/main.css
@@ -1,8 +1,29 @@
+:root {
+ --color-text: #999;
+ --color-text-dimmed: #666;
+ --color-text-bright: #fff;
+ --color-background: #000;
+
+ --font-primary: "Roboto Condensed";
+ --font-secondary: "Roboto";
+
+ --font-size: 20px;
+ --font-size-small: 0.75rem;
+
+ --gap-body-top: 60px;
+ --gap-body-right: 60px;
+ --gap-body-bottom: 60px;
+ --gap-body-left: 60px;
+
+ --gap-modules: 30px;
+}
+
html {
cursor: none;
overflow: hidden;
- background: #000;
+ background: var(--color-background);
user-select: none;
+ font-size: var(--font-size);
}
::-webkit-scrollbar {
@@ -10,16 +31,15 @@ html {
}
body {
- margin: 60px;
+ margin: var(--gap-body-top) var(--gap-body-right) var(--gap-body-bottom) var(--gap-body-left);
position: absolute;
- height: calc(100% - 120px);
- width: calc(100% - 120px);
- background: #000;
- color: #aaa;
- font-family: "Roboto Condensed", sans-serif;
+ height: calc(100% - var(--gap-body-top) - var(--gap-body-bottom));
+ width: calc(100% - var(--gap-body-right) - var(--gap-body-left));
+ background: var(--color-background);
+ color: var(--color-text);
+ font-family: var(--font-primary), sans-serif;
font-weight: 400;
- font-size: 2em;
- line-height: 1.5em;
+ line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
@@ -28,60 +48,60 @@ body {
*/
.dimmed {
- color: #666;
+ color: var(--color-text-dimmed);
}
.normal {
- color: #999;
+ color: var(--color-text);
}
.bright {
- color: #fff;
+ color: var(--color-text-bright);
}
.xsmall {
- font-size: 15px;
- line-height: 20px;
+ font-size: var(--font-size-small);
+ line-height: 1.275;
}
.small {
- font-size: 20px;
- line-height: 25px;
+ font-size: 1rem;
+ line-height: 1.25;
}
.medium {
- font-size: 30px;
- line-height: 35px;
+ font-size: 1.5rem;
+ line-height: 1.225;
}
.large {
- font-size: 65px;
- line-height: 65px;
+ font-size: 3.25rem;
+ line-height: 1;
}
.xlarge {
- font-size: 75px;
- line-height: 75px;
+ font-size: 3.75rem;
+ line-height: 1;
letter-spacing: -3px;
}
.thin {
- font-family: Roboto, sans-serif;
+ font-family: var(--font-secondary), sans-serif;
font-weight: 100;
}
.light {
- font-family: "Roboto Condensed", sans-serif;
+ font-family: var(--font-primary), sans-serif;
font-weight: 300;
}
.regular {
- font-family: "Roboto Condensed", sans-serif;
+ font-family: var(--font-primary), sans-serif;
font-weight: 400;
}
.bold {
- font-family: "Roboto Condensed", sans-serif;
+ font-family: var(--font-primary), sans-serif;
font-weight: 700;
}
@@ -95,14 +115,14 @@ body {
header {
text-transform: uppercase;
- font-size: 15px;
- font-family: "Roboto Condensed", Arial, Helvetica, sans-serif;
+ font-size: var(--font-size-small);
+ font-family: var(--font-primary), Arial, Helvetica, sans-serif;
font-weight: 400;
- border-bottom: 1px solid #666;
+ border-bottom: 1px solid var(--color-text-dimmed);
line-height: 15px;
padding-bottom: 5px;
margin-bottom: 10px;
- color: #999;
+ color: var(--color-text);
}
sup {
@@ -115,11 +135,11 @@ sup {
*/
.module {
- margin-bottom: 30px;
+ margin-bottom: var(--gap-modules);
}
.region.bottom .module {
- margin-top: 30px;
+ margin-top: var(--gap-modules);
margin-bottom: 0;
}
@@ -143,10 +163,10 @@ sup {
.region.fullscreen {
position: absolute;
- top: -60px;
- left: -60px;
- right: -60px;
- bottom: -60px;
+ top: calc(-1 * var(--gap-body-top));
+ left: calc(-1 * var(--gap-body-left));
+ right: calc(-1 * var(--gap-body-right));
+ bottom: calc(-1 * var(--gap-body-bottom));
pointer-events: none;
}
@@ -163,18 +183,6 @@ sup {
top: 0;
}
-.region.top .container {
- margin-bottom: 25px;
-}
-
-.region.bottom .container {
- margin-top: 25px;
-}
-
-.region.top .container:empty {
- margin-bottom: 0;
-}
-
.region.top.center,
.region.bottom.center {
left: 50%;
@@ -191,10 +199,6 @@ sup {
bottom: 0;
}
-.region.bottom .container:empty {
- margin-top: 0;
-}
-
.region.bottom.right,
.region.bottom.center,
.region.bottom.left {
diff --git a/fonts/package-lock.json b/fonts/package-lock.json
index 960e793a..2f944edf 100644
--- a/fonts/package-lock.json
+++ b/fonts/package-lock.json
@@ -1,12 +1,12 @@
{
- "name": "magicmirror-fonts",
- "requires": true,
- "lockfileVersion": 1,
- "dependencies": {
- "roboto-fontface": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
- "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
- }
- }
+ "name": "magicmirror-fonts",
+ "requires": true,
+ "lockfileVersion": 1,
+ "dependencies": {
+ "roboto-fontface": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
+ "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
+ }
+ }
}
diff --git a/index.html b/index.html
index 5a46bb72..c9f2239c 100644
--- a/index.html
+++ b/index.html
@@ -1,55 +1,57 @@
-
- MagicMirror²
-
-
+
+ MagicMirror²
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/check_config.js b/js/check_config.js
index 01cd08e2..60b4cdf3 100644
--- a/js/check_config.js
+++ b/js/check_config.js
@@ -52,7 +52,13 @@ function checkConfigFile() {
// I'm not sure if all ever is utf-8
const configFile = fs.readFileSync(configFileName, "utf-8");
- const errors = linter.verify(configFile);
+ // Explicitly tell linter that he might encounter es6 syntax ("let config = {...}")
+ const errors = linter.verify(configFile, {
+ env: {
+ es6: true
+ }
+ });
+
if (errors.length === 0) {
Log.info(Utils.colors.pass("Your configuration file doesn't contain syntax errors :)"));
} else {
diff --git a/js/defaults.js b/js/defaults.js
index 8a890bc5..0173a594 100644
--- a/js/defaults.js
+++ b/js/defaults.js
@@ -6,12 +6,12 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
-var address = "localhost";
-var port = 8080;
+const address = "localhost";
+let port = 8080;
if (typeof mmPort !== "undefined") {
port = mmPort;
}
-var defaults = {
+const defaults = {
address: address,
port: port,
basePath: "/",
diff --git a/js/loader.js b/js/loader.js
index f290ff44..e3c88be4 100644
--- a/js/loader.js
+++ b/js/loader.js
@@ -6,24 +6,24 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
-var Loader = (function () {
+const Loader = (function () {
/* Create helper variables */
- var loadedModuleFiles = [];
- var loadedFiles = [];
- var moduleObjects = [];
+ const loadedModuleFiles = [];
+ const loadedFiles = [];
+ const moduleObjects = [];
/* Private Methods */
/**
* Loops thru all modules and requests load for every module.
*/
- var loadModules = function () {
- var moduleData = getModuleData();
+ const loadModules = function () {
+ let moduleData = getModuleData();
- var loadNextModule = function () {
+ const loadNextModule = function () {
if (moduleData.length > 0) {
- var nextModule = moduleData[0];
+ const nextModule = moduleData[0];
loadModule(nextModule, function () {
moduleData = moduleData.slice(1);
loadNextModule();
@@ -46,9 +46,8 @@ var Loader = (function () {
/**
* Loops thru all modules and requests start for every module.
*/
- var startModules = function () {
- for (var m in moduleObjects) {
- var module = moduleObjects[m];
+ const startModules = function () {
+ for (const module of moduleObjects) {
module.start();
}
@@ -56,7 +55,7 @@ var Loader = (function () {
MM.modulesStarted(moduleObjects);
// Starting modules also hides any modules that have requested to be initially hidden
- for (let thisModule of moduleObjects) {
+ for (const thisModule of moduleObjects) {
if (thisModule.data.hiddenOnStartup) {
Log.info("Initially hiding " + thisModule.name);
thisModule.hide();
@@ -69,7 +68,7 @@ var Loader = (function () {
*
* @returns {object[]} module data as configured in config
*/
- var getAllModules = function () {
+ const getAllModules = function () {
return config.modules;
};
@@ -78,29 +77,28 @@ var Loader = (function () {
*
* @returns {object[]} Module information.
*/
- var getModuleData = function () {
- var modules = getAllModules();
- var moduleFiles = [];
+ const getModuleData = function () {
+ const modules = getAllModules();
+ const moduleFiles = [];
- for (var m in modules) {
- var moduleData = modules[m];
- var module = moduleData.module;
+ modules.forEach(function (moduleData, index) {
+ const module = moduleData.module;
- var elements = module.split("/");
- var moduleName = elements[elements.length - 1];
- var moduleFolder = config.paths.modules + "/" + module;
+ const elements = module.split("/");
+ const moduleName = elements[elements.length - 1];
+ let moduleFolder = config.paths.modules + "/" + module;
if (defaultModules.indexOf(moduleName) !== -1) {
moduleFolder = config.paths.modules + "/default/" + module;
}
if (moduleData.disabled === true) {
- continue;
+ return;
}
moduleFiles.push({
- index: m,
- identifier: "module_" + m + "_" + module,
+ index: index,
+ identifier: "module_" + index + "_" + module,
name: moduleName,
path: moduleFolder + "/",
file: moduleName + ".js",
@@ -111,7 +109,7 @@ var Loader = (function () {
config: moduleData.config,
classes: typeof moduleData.classes !== "undefined" ? moduleData.classes + " " + module : module
});
- }
+ });
return moduleFiles;
};
@@ -122,11 +120,11 @@ var Loader = (function () {
* @param {object} module Information about the module we want to load.
* @param {Function} callback Function called when done.
*/
- var loadModule = function (module, callback) {
- var url = module.path + module.file;
+ const loadModule = function (module, callback) {
+ const url = module.path + module.file;
- var afterLoad = function () {
- var moduleObject = Module.create(module.name);
+ const afterLoad = function () {
+ const moduleObject = Module.create(module.name);
if (moduleObject) {
bootstrapModule(module, moduleObject, function () {
callback();
@@ -153,7 +151,7 @@ var Loader = (function () {
* @param {Module} mObj Modules instance.
* @param {Function} callback Function called when done.
*/
- var bootstrapModule = function (module, mObj, callback) {
+ const bootstrapModule = function (module, mObj, callback) {
Log.info("Bootstrapping module: " + module.name);
mObj.setData(module);
@@ -177,13 +175,14 @@ var Loader = (function () {
* @param {string} fileName Path of the file we want to load.
* @param {Function} callback Function called when done.
*/
- var loadFile = function (fileName, callback) {
- var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
+ const loadFile = function (fileName, callback) {
+ const extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
+ let script, stylesheet;
switch (extension.toLowerCase()) {
case "js":
Log.log("Load script: " + fileName);
- var script = document.createElement("script");
+ script = document.createElement("script");
script.type = "text/javascript";
script.src = fileName;
script.onload = function () {
@@ -202,7 +201,7 @@ var Loader = (function () {
break;
case "css":
Log.log("Load stylesheet: " + fileName);
- var stylesheet = document.createElement("link");
+ stylesheet = document.createElement("link");
stylesheet.rel = "stylesheet";
stylesheet.type = "text/css";
stylesheet.href = fileName;
diff --git a/js/main.js b/js/main.js
index 6f5d9484..c3141fa9 100644
--- a/js/main.js
+++ b/js/main.js
@@ -6,25 +6,25 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
-var MM = (function () {
- var modules = [];
+const MM = (function () {
+ let modules = [];
/* Private Methods */
/**
* Create dom objects for all modules that are configured for a specific position.
*/
- var createDomObjects = function () {
- var domCreationPromises = [];
+ const createDomObjects = function () {
+ const domCreationPromises = [];
modules.forEach(function (module) {
if (typeof module.data.position !== "string") {
return;
}
- var wrapper = selectWrapper(module.data.position);
+ const wrapper = selectWrapper(module.data.position);
- var dom = document.createElement("div");
+ const dom = document.createElement("div");
dom.id = module.identifier;
dom.className = module.name;
@@ -35,7 +35,7 @@ var MM = (function () {
dom.opacity = 0;
wrapper.appendChild(dom);
- var moduleHeader = document.createElement("header");
+ const moduleHeader = document.createElement("header");
moduleHeader.innerHTML = module.getHeader();
moduleHeader.className = "module-header";
dom.appendChild(moduleHeader);
@@ -46,11 +46,11 @@ var MM = (function () {
moduleHeader.style.display = "block;";
}
- var moduleContent = document.createElement("div");
+ const moduleContent = document.createElement("div");
moduleContent.className = "module-content";
dom.appendChild(moduleContent);
- var domCreationPromise = updateDom(module, 0);
+ const domCreationPromise = updateDom(module, 0);
domCreationPromises.push(domCreationPromise);
domCreationPromise
.then(function () {
@@ -73,11 +73,11 @@ var MM = (function () {
*
* @returns {HTMLElement} the wrapper element
*/
- var selectWrapper = function (position) {
- var classes = position.replace("_", " ");
- var parentWrapper = document.getElementsByClassName(classes);
+ const selectWrapper = function (position) {
+ const classes = position.replace("_", " ");
+ const parentWrapper = document.getElementsByClassName(classes);
if (parentWrapper.length > 0) {
- var wrapper = parentWrapper[0].getElementsByClassName("container");
+ const wrapper = parentWrapper[0].getElementsByClassName("container");
if (wrapper.length > 0) {
return wrapper[0];
}
@@ -92,9 +92,9 @@ var MM = (function () {
* @param {Module} sender The module that sent the notification.
* @param {Module} [sendTo] The (optional) module to send the notification to.
*/
- var sendNotification = function (notification, payload, sender, sendTo) {
- for (var m in modules) {
- var module = modules[m];
+ const sendNotification = function (notification, payload, sender, sendTo) {
+ for (const m in modules) {
+ const module = modules[m];
if (module !== sender && (!sendTo || module === sendTo)) {
module.notificationReceived(notification, payload, sender);
}
@@ -109,10 +109,10 @@ var MM = (function () {
*
* @returns {Promise} Resolved when the dom is fully updated.
*/
- var updateDom = function (module, speed) {
+ const updateDom = function (module, speed) {
return new Promise(function (resolve) {
- var newContentPromise = module.getDom();
- var newHeader = module.getHeader();
+ const newHeader = module.getHeader();
+ let newContentPromise = module.getDom();
if (!(newContentPromise instanceof Promise)) {
// convert to a promise if not already one to avoid if/else's everywhere
@@ -121,7 +121,7 @@ var MM = (function () {
newContentPromise
.then(function (newContent) {
- var updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
+ const updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
updatePromise.then(resolve).catch(Log.error);
})
@@ -139,7 +139,7 @@ var MM = (function () {
*
* @returns {Promise} Resolved when the module dom has been updated.
*/
- var updateDomWithContent = function (module, speed, newHeader, newContent) {
+ const updateDomWithContent = function (module, speed, newHeader, newContent) {
return new Promise(function (resolve) {
if (module.hidden || !speed) {
updateModuleContent(module, newHeader, newContent);
@@ -177,23 +177,23 @@ var MM = (function () {
*
* @returns {boolean} True if the module need an update, false otherwise
*/
- var moduleNeedsUpdate = function (module, newHeader, newContent) {
- var moduleWrapper = document.getElementById(module.identifier);
+ const moduleNeedsUpdate = function (module, newHeader, newContent) {
+ const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {
return false;
}
- var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
- var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
+ const contentWrapper = moduleWrapper.getElementsByClassName("module-content");
+ const headerWrapper = moduleWrapper.getElementsByClassName("module-header");
- var headerNeedsUpdate = false;
- var contentNeedsUpdate = false;
+ let headerNeedsUpdate = false;
+ let contentNeedsUpdate;
if (headerWrapper.length > 0) {
headerNeedsUpdate = newHeader !== headerWrapper[0].innerHTML;
}
- var tempContentWrapper = document.createElement("div");
+ const tempContentWrapper = document.createElement("div");
tempContentWrapper.appendChild(newContent);
contentNeedsUpdate = tempContentWrapper.innerHTML !== contentWrapper[0].innerHTML;
@@ -207,13 +207,13 @@ var MM = (function () {
* @param {string} newHeader The new header that is generated.
* @param {HTMLElement} newContent The new content that is generated.
*/
- var updateModuleContent = function (module, newHeader, newContent) {
- var moduleWrapper = document.getElementById(module.identifier);
+ const updateModuleContent = function (module, newHeader, newContent) {
+ const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {
return;
}
- var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
- var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
+ const headerWrapper = moduleWrapper.getElementsByClassName("module-header");
+ const contentWrapper = moduleWrapper.getElementsByClassName("module-content");
contentWrapper[0].innerHTML = "";
contentWrapper[0].appendChild(newContent);
@@ -234,7 +234,7 @@ var MM = (function () {
* @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) {
+ const hideModule = function (module, speed, callback, options) {
options = options || {};
// set lockString if set in options.
@@ -245,7 +245,7 @@ var MM = (function () {
}
}
- var moduleWrapper = document.getElementById(module.identifier);
+ const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
moduleWrapper.style.opacity = 0;
@@ -280,12 +280,12 @@ var MM = (function () {
* @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) {
+ const showModule = function (module, speed, callback, options) {
options = options || {};
// remove lockString if set in options.
if (options.lockString) {
- var index = module.lockStrings.indexOf(options.lockString);
+ const index = module.lockStrings.indexOf(options.lockString);
if (index !== -1) {
module.lockStrings.splice(index, 1);
}
@@ -309,7 +309,7 @@ var MM = (function () {
module.lockStrings = [];
}
- var moduleWrapper = document.getElementById(module.identifier);
+ const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
// Restore the position. See hideModule() for more info.
@@ -318,7 +318,7 @@ var MM = (function () {
updateWrapperStates();
// Waiting for DOM-changes done in updateWrapperStates before we can start the animation.
- var dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
+ const dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
moduleWrapper.style.opacity = 1;
clearTimeout(module.showHideTimer);
@@ -346,14 +346,14 @@ var MM = (function () {
* an ugly top margin. By using this function, the top bar will be hidden if the
* update notification is not visible.
*/
- var updateWrapperStates = function () {
- var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
+ const updateWrapperStates = function () {
+ const positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
positions.forEach(function (position) {
- var wrapper = selectWrapper(position);
- var moduleWrappers = wrapper.getElementsByClassName("module");
+ const wrapper = selectWrapper(position);
+ const moduleWrappers = wrapper.getElementsByClassName("module");
- var showWrapper = false;
+ let showWrapper = false;
Array.prototype.forEach.call(moduleWrappers, function (moduleWrapper) {
if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") {
showWrapper = true;
@@ -367,7 +367,7 @@ var MM = (function () {
/**
* Loads the core config and combines it with the system defaults.
*/
- var loadConfig = function () {
+ const loadConfig = function () {
// FIXME: Think about how to pass config around without breaking tests
/* eslint-disable */
if (typeof config === "undefined") {
@@ -385,7 +385,7 @@ var MM = (function () {
*
* @param {Module[]} modules Array of modules.
*/
- var setSelectionMethodsForModules = function (modules) {
+ const setSelectionMethodsForModules = function (modules) {
/**
* Filter modules with the specified classes.
*
@@ -393,7 +393,7 @@ var MM = (function () {
*
* @returns {Module[]} Filtered collection of modules.
*/
- var withClass = function (className) {
+ const withClass = function (className) {
return modulesByClass(className, true);
};
@@ -404,7 +404,7 @@ var MM = (function () {
*
* @returns {Module[]} Filtered collection of modules.
*/
- var exceptWithClass = function (className) {
+ const exceptWithClass = function (className) {
return modulesByClass(className, false);
};
@@ -416,17 +416,16 @@ var MM = (function () {
*
* @returns {Module[]} Filtered collection of modules.
*/
- var modulesByClass = function (className, include) {
- var searchClasses = className;
+ const modulesByClass = function (className, include) {
+ let searchClasses = className;
if (typeof className === "string") {
searchClasses = className.split(" ");
}
- var newModules = modules.filter(function (module) {
- var classes = module.data.classes.toLowerCase().split(" ");
+ const newModules = modules.filter(function (module) {
+ const classes = module.data.classes.toLowerCase().split(" ");
- for (var c in searchClasses) {
- var searchClass = searchClasses[c];
+ for (const searchClass of searchClasses) {
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
return include;
}
@@ -445,8 +444,8 @@ var MM = (function () {
* @param {object} module The module instance to remove from the collection.
* @returns {Module[]} Filtered collection of modules.
*/
- var exceptModule = function (module) {
- var newModules = modules.filter(function (mod) {
+ const exceptModule = function (module) {
+ const newModules = modules.filter(function (mod) {
return mod.identifier !== module.identifier;
});
@@ -459,7 +458,7 @@ var MM = (function () {
*
* @param {Function} callback The function to execute with the module as an argument.
*/
- var enumerate = function (callback) {
+ const enumerate = function (callback) {
modules.map(function (module) {
callback(module);
});
@@ -604,11 +603,11 @@ if (typeof Object.assign !== "function") {
if (target === undefined || target === null) {
throw new TypeError("Cannot convert undefined or null to object");
}
- var output = Object(target);
- for (var index = 1; index < arguments.length; index++) {
- var source = arguments[index];
+ const output = Object(target);
+ for (let index = 1; index < arguments.length; index++) {
+ const source = arguments[index];
if (source !== undefined && source !== null) {
- for (var nextKey in source) {
+ for (const nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
output[nextKey] = source[nextKey];
}
diff --git a/js/module.js b/js/module.js
index 042b674b..b754e197 100644
--- a/js/module.js
+++ b/js/module.js
@@ -6,7 +6,6 @@
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
- *
*/
var Module = Class.extend({
/*********************************************************
@@ -82,16 +81,15 @@ var Module = Class.extend({
* @returns {HTMLElement|Promise} The dom or a promise with the dom to display.
*/
getDom: function () {
- var self = this;
- return new Promise(function (resolve) {
- var div = document.createElement("div");
- var template = self.getTemplate();
- var templateData = self.getTemplateData();
+ return new Promise((resolve) => {
+ const div = document.createElement("div");
+ const template = this.getTemplate();
+ const templateData = this.getTemplateData();
// Check to see if we need to render a template string or a file.
if (/^.*((\.html)|(\.njk))$/.test(template)) {
// the template is a filename
- self.nunjucksEnvironment().render(template, templateData, function (err, res) {
+ this.nunjucksEnvironment().render(template, templateData, function (err, res) {
if (err) {
Log.error(err);
}
@@ -102,7 +100,7 @@ var Module = Class.extend({
});
} else {
// the template is a template string.
- div.innerHTML = self.nunjucksEnvironment().renderString(template, templateData);
+ div.innerHTML = this.nunjucksEnvironment().renderString(template, templateData);
resolve(div);
}
@@ -168,15 +166,13 @@ var Module = Class.extend({
return this._nunjucksEnvironment;
}
- var self = this;
-
this._nunjucksEnvironment = new nunjucks.Environment(new nunjucks.WebLoader(this.file(""), { async: true }), {
trimBlocks: true,
lstripBlocks: true
});
- this._nunjucksEnvironment.addFilter("translate", function (str, variables) {
- return nunjucks.runtime.markSafe(self.translate(str, variables));
+ this._nunjucksEnvironment.addFilter("translate", (str, variables) => {
+ return nunjucks.runtime.markSafe(this.translate(str, variables));
});
return this._nunjucksEnvironment;
@@ -192,14 +188,14 @@ var Module = Class.extend({
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
},
- /*
+ /**
* Called when the module is hidden.
*/
suspend: function () {
Log.log(this.name + " is suspended.");
},
- /*
+ /**
* Called when the module is shown.
*/
resume: function () {
@@ -213,7 +209,7 @@ var Module = Class.extend({
/**
* Set the module data.
*
- * @param {Module} data The module data
+ * @param {object} data The module data
*/
setData: function (data) {
this.data = data;
@@ -245,9 +241,8 @@ var Module = Class.extend({
this._socket = new MMSocket(this.name);
}
- var self = this;
- this._socket.setNotificationCallback(function (notification, payload) {
- self.socketNotificationReceived(notification, payload);
+ this._socket.setNotificationCallback((notification, payload) => {
+ this.socketNotificationReceived(notification, payload);
});
return this._socket;
@@ -288,13 +283,12 @@ var Module = Class.extend({
* @param {Function} callback Function called when done.
*/
loadDependencies: function (funcName, callback) {
- var self = this;
- var dependencies = this[funcName]();
+ let dependencies = this[funcName]();
- var loadNextDependency = function () {
+ const loadNextDependency = () => {
if (dependencies.length > 0) {
- var nextDependency = dependencies[0];
- Loader.loadFile(nextDependency, self, function () {
+ const nextDependency = dependencies[0];
+ Loader.loadFile(nextDependency, this, () => {
dependencies = dependencies.slice(1);
loadNextDependency();
});
@@ -400,12 +394,11 @@ var Module = Class.extend({
callback = callback || function () {};
options = options || {};
- var self = this;
MM.hideModule(
- self,
+ this,
speed,
- function () {
- self.suspend();
+ () => {
+ this.suspend();
callback();
},
options
@@ -464,9 +457,9 @@ var Module = Class.extend({
* @returns {object} the merged config
*/
function configMerge(result) {
- var stack = Array.prototype.slice.call(arguments, 1);
- var item;
- var key;
+ const stack = Array.prototype.slice.call(arguments, 1);
+ let item, key;
+
while (stack.length) {
item = stack.shift();
for (key in item) {
@@ -494,11 +487,11 @@ Module.create = function (name) {
return;
}
- var moduleDefinition = Module.definitions[name];
- var clonedDefinition = cloneObject(moduleDefinition);
+ const moduleDefinition = Module.definitions[name];
+ const clonedDefinition = cloneObject(moduleDefinition);
// Note that we clone the definition. Otherwise the objects are shared, which gives problems.
- var ModuleClass = Module.extend(clonedDefinition);
+ const ModuleClass = Module.extend(clonedDefinition);
return new ModuleClass();
};
@@ -526,14 +519,13 @@ Module.register = function (name, moduleDefinition) {
* number if a is smaller and 0 if they are the same
*/
function cmpVersions(a, b) {
- var i, diff;
- var regExStrip0 = /(\.0+)+$/;
- var segmentsA = a.replace(regExStrip0, "").split(".");
- var segmentsB = b.replace(regExStrip0, "").split(".");
- var l = Math.min(segmentsA.length, segmentsB.length);
+ const regExStrip0 = /(\.0+)+$/;
+ const segmentsA = a.replace(regExStrip0, "").split(".");
+ const segmentsB = b.replace(regExStrip0, "").split(".");
+ const l = Math.min(segmentsA.length, segmentsB.length);
- for (i = 0; i < l; i++) {
- diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
+ for (let i = 0; i < l; i++) {
+ let diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
if (diff) {
return diff;
}
diff --git a/js/node_helper.js b/js/node_helper.js
index 81d2d9d5..86ad3aa4 100644
--- a/js/node_helper.js
+++ b/js/node_helper.js
@@ -113,6 +113,32 @@ const NodeHelper = Class.extend({
}
});
+NodeHelper.checkFetchStatus = function (response) {
+ // response.status >= 200 && response.status < 300
+ if (response.ok) {
+ return response;
+ } else {
+ throw Error(response.statusText);
+ }
+};
+
+/**
+ * Look at the specified error and return an appropriate error type, that
+ * can be translated to a detailed error message
+ *
+ * @param {Error} error the error from fetching something
+ * @returns {string} the string of the detailed error message in the translations
+ */
+NodeHelper.checkFetchError = function (error) {
+ let error_type = "MODULE_ERROR_UNSPECIFIED";
+ if (error.code === "EAI_AGAIN") {
+ error_type = "MODULE_ERROR_NO_CONNECTION";
+ } else if (error.message === "Unauthorized") {
+ error_type = "MODULE_ERROR_UNAUTHORIZED";
+ }
+ return error_type;
+};
+
NodeHelper.create = function (moduleDefinition) {
return NodeHelper.extend(moduleDefinition);
};
diff --git a/js/socketclient.js b/js/socketclient.js
index 2a50083f..acb8cfdc 100644
--- a/js/socketclient.js
+++ b/js/socketclient.js
@@ -6,49 +6,48 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
-var MMSocket = function (moduleName) {
- var self = this;
-
+const MMSocket = function (moduleName) {
if (typeof moduleName !== "string") {
throw new Error("Please set the module name for the MMSocket.");
}
- self.moduleName = moduleName;
+ this.moduleName = moduleName;
// Private Methods
- var base = "/";
+ let base = "/";
if (typeof config !== "undefined" && typeof config.basePath !== "undefined") {
base = config.basePath;
}
- self.socket = io("/" + self.moduleName, {
+ this.socket = io("/" + this.moduleName, {
path: base + "socket.io"
});
- var notificationCallback = function () {};
- var onevent = self.socket.onevent;
- self.socket.onevent = function (packet) {
- var args = packet.data || [];
- onevent.call(this, packet); // original call
+ let notificationCallback = function () {};
+
+ const onevent = this.socket.onevent;
+ this.socket.onevent = (packet) => {
+ const args = packet.data || [];
+ onevent.call(this.socket, packet); // original call
packet.data = ["*"].concat(args);
- onevent.call(this, packet); // additional call to catch-all
+ onevent.call(this.socket, packet); // additional call to catch-all
};
// register catch all.
- self.socket.on("*", function (notification, payload) {
+ this.socket.on("*", (notification, payload) => {
if (notification !== "*") {
notificationCallback(notification, payload);
}
});
// Public Methods
- this.setNotificationCallback = function (callback) {
+ this.setNotificationCallback = (callback) => {
notificationCallback = callback;
};
- this.sendNotification = function (notification, payload) {
+ this.sendNotification = (notification, payload) => {
if (typeof payload === "undefined") {
payload = {};
}
- self.socket.emit(notification, payload);
+ this.socket.emit(notification, payload);
};
};
diff --git a/modules/default/alert/alert.js b/modules/default/alert/alert.js
index 8d175b39..2e471ac1 100644
--- a/modules/default/alert/alert.js
+++ b/modules/default/alert/alert.js
@@ -79,7 +79,7 @@ Module.register("alert", {
//If module already has an open alert close it
if (this.alerts[sender.name]) {
- this.hide_alert(sender);
+ this.hide_alert(sender, false);
}
//Display title and message only if they are provided in notification parameters
@@ -114,10 +114,10 @@ Module.register("alert", {
}, params.timer);
}
},
- hide_alert: function (sender) {
+ hide_alert: function (sender, close = true) {
//Dismiss alert and remove from this.alerts
if (this.alerts[sender.name]) {
- this.alerts[sender.name].dismiss();
+ this.alerts[sender.name].dismiss(close);
this.alerts[sender.name] = null;
//Remove overlay
const overlay = document.getElementById("overlay");
diff --git a/modules/default/alert/notificationFx.css b/modules/default/alert/notificationFx.css
index 0782d464..39faacf7 100644
--- a/modules/default/alert/notificationFx.css
+++ b/modules/default/alert/notificationFx.css
@@ -6,7 +6,6 @@
line-height: 1.4;
margin-bottom: 10px;
z-index: 1;
- color: black;
font-size: 70%;
position: relative;
display: table;
@@ -15,17 +14,17 @@
border-width: 1px;
border-radius: 5px;
border-style: solid;
- border-color: #666;
+ border-color: var(--color-text-dimmed);
}
.ns-alert {
border-style: solid;
- border-color: #fff;
+ border-color: var(--color-text-bright);
padding: 17px;
line-height: 1.4;
margin-bottom: 10px;
z-index: 3;
- color: white;
+ color: var(--color-text-bright);
font-size: 70%;
position: fixed;
text-align: center;
diff --git a/modules/default/alert/notificationFx.js b/modules/default/alert/notificationFx.js
index 89034420..317fa75a 100644
--- a/modules/default/alert/notificationFx.js
+++ b/modules/default/alert/notificationFx.js
@@ -122,8 +122,10 @@
/**
* Dismiss the notification
+ *
+ * @param {boolean} [close] call the onClose callback at the end
*/
- NotificationFx.prototype.dismiss = function () {
+ NotificationFx.prototype.dismiss = function (close = true) {
this.active = false;
clearTimeout(this.dismissttl);
this.ntf.classList.remove("ns-show");
@@ -131,7 +133,7 @@
this.ntf.classList.add("ns-hide");
// callback
- this.options.onClose();
+ if (close) this.options.onClose();
}, 25);
// after animation ends remove ntf from the DOM
diff --git a/modules/default/calendar/calendar.css b/modules/default/calendar/calendar.css
index 65908a70..f04d6838 100644
--- a/modules/default/calendar/calendar.css
+++ b/modules/default/calendar/calendar.css
@@ -1,13 +1,14 @@
.calendar .symbol {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
padding-left: 0;
padding-right: 10px;
- font-size: 80%;
- vertical-align: top;
+ font-size: var(--font-size-small);
}
.calendar .symbol span {
- display: inline-block;
- transform: translate(0, 2px);
+ padding-top: 4px;
}
.calendar .title {
diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js
index 292ceab1..7525e3a8 100755
--- a/modules/default/calendar/calendar.js
+++ b/modules/default/calendar/calendar.js
@@ -84,7 +84,7 @@ Module.register("calendar", {
// Override start method.
start: function () {
- Log.log("Starting module: " + this.name);
+ Log.info("Starting module: " + this.name);
// Set locale.
moment.updateLocale(config.language, this.getLocaleSpecification(config.timeFormat));
@@ -140,17 +140,17 @@ Module.register("calendar", {
if (notification === "CALENDAR_EVENTS") {
if (this.hasCalendarURL(payload.url)) {
this.calendarData[payload.url] = payload.events;
+ this.error = null;
this.loaded = true;
if (this.config.broadcastEvents) {
this.broadcastEvents();
}
}
- } else if (notification === "FETCH_ERROR") {
- Log.error("Calendar Error. Could not fetch calendar: " + payload.url);
+ } else if (notification === "CALENDAR_ERROR") {
+ let error_message = this.translate(payload.error_type);
+ this.error = this.translate("MODULE_CONFIG_ERROR", { MODULE_NAME: this.name, ERROR: error_message });
this.loaded = true;
- } else if (notification === "INCORRECT_URL") {
- Log.error("Calendar Error. Incorrect url: " + payload.url);
}
this.updateDom(this.config.animationSpeed);
@@ -168,6 +168,12 @@ Module.register("calendar", {
const wrapper = document.createElement("table");
wrapper.className = this.config.tableClass;
+ if (this.error) {
+ wrapper.innerHTML = this.error;
+ wrapper.className = this.config.tableClass + " dimmed";
+ return wrapper;
+ }
+
if (events.length === 0) {
wrapper.innerHTML = this.loaded ? this.translate("EMPTY") : this.translate("LOADING");
wrapper.className = this.config.tableClass + " dimmed";
@@ -305,15 +311,14 @@ Module.register("calendar", {
if (this.config.timeFormat === "dateheaders") {
if (event.fullDayEvent) {
titleWrapper.colSpan = "2";
- titleWrapper.align = "left";
+ titleWrapper.classList.add("align-left");
} else {
const timeWrapper = document.createElement("td");
- timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
- timeWrapper.align = "left";
+ timeWrapper.className = "time light align-left " + this.timeClassForUrl(event.url);
timeWrapper.style.paddingLeft = "2px";
timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
eventWrapper.appendChild(timeWrapper);
- titleWrapper.align = "right";
+ titleWrapper.classList.add("align-right");
}
eventWrapper.appendChild(titleWrapper);
@@ -366,13 +371,14 @@ Module.register("calendar", {
if (event.startDate >= now) {
// Use relative time
if (!this.config.hideTime) {
- timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
+ timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
} else {
timeWrapper.innerHTML = this.capFirst(
moment(event.startDate, "x").calendar(null, {
sameDay: "[" + this.translate("TODAY") + "]",
nextDay: "[" + this.translate("TOMORROW") + "]",
- nextWeek: "dddd"
+ nextWeek: "dddd",
+ sameElse: this.config.dateFormat
})
);
}
diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js
index 9673fa31..805e080b 100644
--- a/modules/default/calendar/calendarfetcher.js
+++ b/modules/default/calendar/calendarfetcher.js
@@ -6,6 +6,7 @@
*/
const CalendarUtils = require("./calendarutils");
const Log = require("logger");
+const NodeHelper = require("node_helper");
const ical = require("node-ical");
const fetch = require("node-fetch");
const digest = require("digest-fetch");
@@ -52,27 +53,17 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
if (auth.method === "bearer") {
headers.Authorization = "Bearer " + auth.pass;
} else if (auth.method === "digest") {
- fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, httpsAgent: httpsAgent });
+ fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, agent: httpsAgent });
} else {
headers.Authorization = "Basic " + Buffer.from(auth.user + ":" + auth.pass).toString("base64");
}
}
if (fetcher === null) {
- fetcher = fetch(url, { headers: headers, httpsAgent: httpsAgent });
+ fetcher = fetch(url, { headers: headers, agent: httpsAgent });
}
fetcher
- .catch((error) => {
- fetchFailedCallback(this, error);
- scheduleTimer();
- })
- .then((response) => {
- if (response.status !== 200) {
- fetchFailedCallback(this, response.statusText);
- scheduleTimer();
- }
- return response;
- })
+ .then(NodeHelper.checkFetchStatus)
.then((response) => response.text())
.then((responseData) => {
let data = [];
@@ -87,12 +78,16 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
maximumNumberOfDays
});
} catch (error) {
- fetchFailedCallback(this, error.message);
+ fetchFailedCallback(this, error);
scheduleTimer();
return;
}
this.broadcastEvents();
scheduleTimer();
+ })
+ .catch((error) => {
+ fetchFailedCallback(this, error);
+ scheduleTimer();
});
};
diff --git a/modules/default/calendar/calendarutils.js b/modules/default/calendar/calendarutils.js
index 223dfb26..7f0b14b8 100644
--- a/modules/default/calendar/calendarutils.js
+++ b/modules/default/calendar/calendarutils.js
@@ -18,8 +18,8 @@ const CalendarUtils = {
* Calculate the time correction, either dst/std or full day in cases where
* utc time is day before plus offset
*
- * @param {object} event
- * @param {Date} date
+ * @param {object} event the event which needs adjustement
+ * @param {Date} date the date on which this event happens
* @returns {number} the necessary adjustment in hours
*/
calculateTimezoneAdjustment: function (event, date) {
@@ -117,6 +117,13 @@ const CalendarUtils = {
return adjustHours;
},
+ /**
+ * Filter the events from ical according to the given config
+ *
+ * @param {object} data the calendar data from ical
+ * @param {object} config The configuration object
+ * @returns {string[]} the filtered events
+ */
filterEvents: function (data, config) {
const newEvents = [];
@@ -500,8 +507,8 @@ const CalendarUtils = {
/**
* Lookup iana tz from windows
*
- * @param msTZName
- * @returns {*|null}
+ * @param {string} msTZName the timezone name to lookup
+ * @returns {string|null} the iana name or null of none is found
*/
getIanaTZFromMS: function (msTZName) {
// Get hash entry
@@ -571,12 +578,13 @@ const CalendarUtils = {
},
/**
+ * Determines if the user defined title filter should apply
*
- * @param title
- * @param filter
- * @param useRegex
- * @param regexFlags
- * @returns {boolean|*}
+ * @param {string} title the title of the event
+ * @param {string} filter the string to look for, can be a regex also
+ * @param {boolean} useRegex true if a regex should be used, otherwise it just looks for the filter as a string
+ * @param {string} regexFlags flags that should be applied to the regex
+ * @returns {boolean} True if the title should be filtered out, false otherwise
*/
titleFilterApplies: function (title, filter, useRegex, regexFlags) {
if (useRegex) {
diff --git a/modules/default/calendar/debug.js b/modules/default/calendar/debug.js
index f01bcc0d..f2d3a48e 100644
--- a/modules/default/calendar/debug.js
+++ b/modules/default/calendar/debug.js
@@ -5,6 +5,9 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
+// Alias modules mentioned in package.js under _moduleAliases.
+require("module-alias/register");
+
const CalendarFetcher = require("./calendarfetcher.js");
const url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL
@@ -26,11 +29,13 @@ const fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maxi
fetcher.onReceive(function (fetcher) {
console.log(fetcher.events());
console.log("------------------------------------------------------------");
+ process.exit(0);
});
fetcher.onError(function (fetcher, error) {
console.log("Fetcher error:");
console.log(error);
+ process.exit(1);
});
fetcher.startFetch();
diff --git a/modules/default/calendar/node_helper.js b/modules/default/calendar/node_helper.js
index d544878b..5b1a8bad 100644
--- a/modules/default/calendar/node_helper.js
+++ b/modules/default/calendar/node_helper.js
@@ -40,13 +40,14 @@ module.exports = NodeHelper.create({
try {
new URL(url);
} catch (error) {
- this.sendSocketNotification("INCORRECT_URL", { id: identifier, url: url });
+ Log.error("Calendar Error. Malformed calendar url: ", url, error);
+ this.sendSocketNotification("CALENDAR_ERROR", { error_type: "MODULE_ERROR_MALFORMED_URL" });
return;
}
let fetcher;
if (typeof this.fetchers[identifier + url] === "undefined") {
- Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
+ Log.log("Create new calendarfetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert);
fetcher.onReceive((fetcher) => {
@@ -55,16 +56,16 @@ module.exports = NodeHelper.create({
fetcher.onError((fetcher, error) => {
Log.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
- this.sendSocketNotification("FETCH_ERROR", {
+ let error_type = NodeHelper.checkFetchError(error);
+ this.sendSocketNotification("CALENDAR_ERROR", {
id: identifier,
- url: fetcher.url(),
- error: error
+ error_type
});
});
this.fetchers[identifier + url] = fetcher;
} else {
- Log.log("Use existing calendar fetcher for url: " + url);
+ Log.log("Use existing calendarfetcher for url: " + url);
fetcher = this.fetchers[identifier + url];
fetcher.broadcastEvents();
}
diff --git a/modules/default/clock/clock.js b/modules/default/clock/clock.js
index 6e3d7090..fbab09a0 100644
--- a/modules/default/clock/clock.js
+++ b/modules/default/clock/clock.js
@@ -46,62 +46,61 @@ Module.register("clock", {
Log.info("Starting module: " + this.name);
// Schedule update interval.
- var self = this;
- self.second = moment().second();
- self.minute = moment().minute();
+ this.second = moment().second();
+ this.minute = moment().minute();
- //Calculate how many ms should pass until next update depending on if seconds is displayed or not
- var delayCalculator = function (reducedSeconds) {
- var EXTRA_DELAY = 50; //Deliberate imperceptable delay to prevent off-by-one timekeeping errors
+ // Calculate how many ms should pass until next update depending on if seconds is displayed or not
+ const delayCalculator = (reducedSeconds) => {
+ const EXTRA_DELAY = 50; // Deliberate imperceptible delay to prevent off-by-one timekeeping errors
- if (self.config.displaySeconds) {
+ if (this.config.displaySeconds) {
return 1000 - moment().milliseconds() + EXTRA_DELAY;
} else {
return (60 - reducedSeconds) * 1000 - moment().milliseconds() + EXTRA_DELAY;
}
};
- //A recursive timeout function instead of interval to avoid drifting
- var notificationTimer = function () {
- self.updateDom();
+ // A recursive timeout function instead of interval to avoid drifting
+ const notificationTimer = () => {
+ this.updateDom();
- //If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
- if (self.config.displaySeconds) {
- self.second = moment().second();
- if (self.second !== 0) {
- self.sendNotification("CLOCK_SECOND", self.second);
+ // If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
+ if (this.config.displaySeconds) {
+ this.second = moment().second();
+ if (this.second !== 0) {
+ this.sendNotification("CLOCK_SECOND", this.second);
setTimeout(notificationTimer, delayCalculator(0));
return;
}
}
- //If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification
- self.minute = moment().minute();
- self.sendNotification("CLOCK_MINUTE", self.minute);
+ // If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification
+ this.minute = moment().minute();
+ this.sendNotification("CLOCK_MINUTE", this.minute);
setTimeout(notificationTimer, delayCalculator(0));
};
- //Set the initial timeout with the amount of seconds elapsed as reducedSeconds so it will trigger when the minute changes
- setTimeout(notificationTimer, delayCalculator(self.second));
+ // Set the initial timeout with the amount of seconds elapsed as reducedSeconds so it will trigger when the minute changes
+ setTimeout(notificationTimer, delayCalculator(this.second));
// Set locale.
moment.locale(config.language);
},
// Override dom generator.
getDom: function () {
- var wrapper = document.createElement("div");
+ const wrapper = document.createElement("div");
/************************************
* Create wrappers for DIGITAL clock
*/
- var dateWrapper = document.createElement("div");
- var timeWrapper = document.createElement("div");
- var secondsWrapper = document.createElement("sup");
- var periodWrapper = document.createElement("span");
- var sunWrapper = document.createElement("div");
- var moonWrapper = document.createElement("div");
- var weekWrapper = document.createElement("div");
+ const dateWrapper = document.createElement("div");
+ const timeWrapper = document.createElement("div");
+ const secondsWrapper = document.createElement("sup");
+ const periodWrapper = document.createElement("span");
+ const sunWrapper = document.createElement("div");
+ const moonWrapper = document.createElement("div");
+ const weekWrapper = document.createElement("div");
// Style Wrappers
dateWrapper.className = "date normal medium";
timeWrapper.className = "time bright large light";
@@ -114,14 +113,13 @@ Module.register("clock", {
// The moment().format("h") method has a bug on the Raspberry Pi.
// So we need to generate the timestring manually.
// See issue: https://github.com/MichMich/MagicMirror/issues/181
- var timeString;
- var now = moment();
- this.lastDisplayedMinute = now.minute();
+ let timeString;
+ const now = moment();
if (this.config.timezone) {
now.tz(this.config.timezone);
}
- var hourSymbol = "HH";
+ let hourSymbol = "HH";
if (this.config.timeFormat !== 24) {
hourSymbol = "h";
}
@@ -160,7 +158,7 @@ Module.register("clock", {
* @returns {string} The formatted time string
*/
function formatTime(config, time) {
- var formatString = hourSymbol + ":mm";
+ let formatString = hourSymbol + ":mm";
if (config.showPeriod && config.timeFormat !== 24) {
formatString += config.showPeriodUpper ? "A" : "a";
}
@@ -170,7 +168,7 @@ Module.register("clock", {
if (this.config.showSunTimes) {
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
- var nextEvent;
+ let nextEvent;
if (now.isBefore(sunTimes.sunrise)) {
nextEvent = sunTimes.sunrise;
} else if (now.isBefore(sunTimes.sunset)) {
@@ -198,7 +196,7 @@ Module.register("clock", {
const moonIllumination = SunCalc.getMoonIllumination(now.toDate());
const moonTimes = SunCalc.getMoonTimes(now, this.config.lat, this.config.lon);
const moonRise = moonTimes.rise;
- var moonSet;
+ let moonSet;
if (moment(moonTimes.set).isAfter(moonTimes.rise)) {
moonSet = moonTimes.set;
} else {
@@ -224,6 +222,7 @@ Module.register("clock", {
/****************************************************************
* Create wrappers for ANALOG clock, only if specified in config
*/
+ const clockCircle = document.createElement("div");
if (this.config.displayType !== "digital") {
// If it isn't 'digital', then an 'analog' clock was also requested
@@ -232,12 +231,11 @@ Module.register("clock", {
if (this.config.timezone) {
now.tz(this.config.timezone);
}
- var second = now.seconds() * 6,
+ const second = now.seconds() * 6,
minute = now.minute() * 6 + second / 60,
hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12;
// Create wrappers
- var clockCircle = document.createElement("div");
clockCircle.className = "clockCircle";
clockCircle.style.width = this.config.analogSize;
clockCircle.style.height = this.config.analogSize;
@@ -252,14 +250,14 @@ Module.register("clock", {
} else if (this.config.analogFace !== "none") {
clockCircle.style.border = "2px solid white";
}
- var clockFace = document.createElement("div");
+ const clockFace = document.createElement("div");
clockFace.className = "clockFace";
- var clockHour = document.createElement("div");
+ const clockHour = document.createElement("div");
clockHour.id = "clockHour";
clockHour.style.transform = "rotate(" + hour + "deg)";
clockHour.className = "clockHour";
- var clockMinute = document.createElement("div");
+ const clockMinute = document.createElement("div");
clockMinute.id = "clockMinute";
clockMinute.style.transform = "rotate(" + minute + "deg)";
clockMinute.className = "clockMinute";
@@ -269,7 +267,7 @@ Module.register("clock", {
clockFace.appendChild(clockMinute);
if (this.config.displaySeconds) {
- var clockSecond = document.createElement("div");
+ const clockSecond = document.createElement("div");
clockSecond.id = "clockSecond";
clockSecond.style.transform = "rotate(" + second + "deg)";
clockSecond.className = "clockSecond";
@@ -312,14 +310,14 @@ Module.register("clock", {
}
} else {
// Both clocks have been configured, check position
- var placement = this.config.analogPlacement;
+ const placement = this.config.analogPlacement;
- var analogWrapper = document.createElement("div");
+ const analogWrapper = document.createElement("div");
analogWrapper.id = "analog";
analogWrapper.style.cssFloat = "none";
analogWrapper.appendChild(clockCircle);
- var digitalWrapper = document.createElement("div");
+ const digitalWrapper = document.createElement("div");
digitalWrapper.id = "digital";
digitalWrapper.style.cssFloat = "none";
digitalWrapper.appendChild(dateWrapper);
@@ -328,8 +326,8 @@ Module.register("clock", {
digitalWrapper.appendChild(moonWrapper);
digitalWrapper.appendChild(weekWrapper);
- var appendClocks = function (condition, pos1, pos2) {
- var padding = [0, 0, 0, 0];
+ const appendClocks = (condition, pos1, pos2) => {
+ const padding = [0, 0, 0, 0];
padding[placement === condition ? pos1 : pos2] = "20px";
analogWrapper.style.padding = padding.join(" ");
if (placement === condition) {
diff --git a/modules/default/clock/clock_styles.css b/modules/default/clock/clock_styles.css
index 839336be..0e74fd7a 100644
--- a/modules/default/clock/clock_styles.css
+++ b/modules/default/clock/clock_styles.css
@@ -17,7 +17,7 @@
width: 6px;
height: 6px;
margin: -3px 0 0 -3px;
- background: white;
+ background: var(--color-text-bright);
border-radius: 3px;
content: "";
display: block;
@@ -29,9 +29,9 @@
position: absolute;
top: 50%;
left: 50%;
- margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
+ margin: -2px 0 -2px -25%; /* numbers must match negative length & thickness */
padding: 2px 0 2px 25%; /* indicator length & thickness */
- background: white;
+ background: var(--color-text-bright);
transform-origin: 100% 50%;
border-radius: 3px 0 0 3px;
}
@@ -44,7 +44,7 @@
left: 50%;
margin: -35% -2px 0; /* numbers must match negative length & thickness */
padding: 35% 2px 0; /* indicator length & thickness */
- background: white;
+ background: var(--color-text-bright);
transform-origin: 50% 100%;
border-radius: 3px 0 0 3px;
}
@@ -57,7 +57,7 @@
left: 50%;
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
padding: 38% 1px 0 0; /* indicator length & thickness */
- background: #888;
+ background: var(--color-text);
transform-origin: 50% 100%;
}
diff --git a/modules/default/compliments/compliments.js b/modules/default/compliments/compliments.js
index 6613a2c8..054a409f 100644
--- a/modules/default/compliments/compliments.js
+++ b/modules/default/compliments/compliments.js
@@ -39,37 +39,35 @@ Module.register("compliments", {
this.lastComplimentIndex = -1;
- var self = this;
if (this.config.remoteFile !== null) {
- this.complimentFile(function (response) {
- self.config.compliments = JSON.parse(response);
- self.updateDom();
+ this.complimentFile((response) => {
+ this.config.compliments = JSON.parse(response);
+ this.updateDom();
});
}
// Schedule update timer.
- setInterval(function () {
- self.updateDom(self.config.fadeSpeed);
+ setInterval(() => {
+ this.updateDom(this.config.fadeSpeed);
}, this.config.updateInterval);
},
- /* randomIndex(compliments)
+ /**
* Generate a random index for a list of compliments.
*
- * argument compliments Array - Array with compliments.
- *
- * return Number - Random index.
+ * @param {string[]} compliments Array with compliments.
+ * @returns {number} a random index of given array
*/
randomIndex: function (compliments) {
if (compliments.length === 1) {
return 0;
}
- var generate = function () {
+ const generate = function () {
return Math.floor(Math.random() * compliments.length);
};
- var complimentIndex = generate();
+ let complimentIndex = generate();
while (complimentIndex === this.lastComplimentIndex) {
complimentIndex = generate();
@@ -80,15 +78,15 @@ Module.register("compliments", {
return complimentIndex;
},
- /* complimentArray()
+ /**
* Retrieve an array of compliments for the time of the day.
*
- * return compliments Array - Array with compliments for the time of the day.
+ * @returns {string[]} array with compliments for the time of the day.
*/
complimentArray: function () {
- var hour = moment().hour();
- var date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD");
- var compliments;
+ const hour = moment().hour();
+ const date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD");
+ let compliments;
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) {
compliments = this.config.compliments.morning.slice(0);
@@ -99,7 +97,7 @@ Module.register("compliments", {
}
if (typeof compliments === "undefined") {
- compliments = new Array();
+ compliments = [];
}
if (this.currentWeatherType in this.config.compliments) {
@@ -108,7 +106,7 @@ Module.register("compliments", {
compliments.push.apply(compliments, this.config.compliments.anytime);
- for (var entry in this.config.compliments) {
+ for (let entry in this.config.compliments) {
if (new RegExp(entry).test(date)) {
compliments.push.apply(compliments, this.config.compliments[entry]);
}
@@ -117,11 +115,13 @@ Module.register("compliments", {
return compliments;
},
- /* complimentFile(callback)
+ /**
* Retrieve a file from the local filesystem
+ *
+ * @param {Function} callback Called when the file is retrieved.
*/
complimentFile: function (callback) {
- var xobj = new XMLHttpRequest(),
+ const xobj = new XMLHttpRequest(),
isRemote = this.config.remoteFile.indexOf("http://") === 0 || this.config.remoteFile.indexOf("https://") === 0,
path = isRemote ? this.config.remoteFile : this.file(this.config.remoteFile);
xobj.overrideMimeType("application/json");
@@ -134,16 +134,16 @@ Module.register("compliments", {
xobj.send(null);
},
- /* complimentArray()
+ /**
* Retrieve a random compliment.
*
- * return compliment string - A compliment.
+ * @returns {string} a compliment
*/
randomCompliment: function () {
// get the current time of day compliments list
- var compliments = this.complimentArray();
+ const compliments = this.complimentArray();
// variable for index to next message to display
- let index = 0;
+ let index;
// are we randomizing
if (this.config.random) {
// yes
@@ -159,16 +159,16 @@ Module.register("compliments", {
// Override dom generator.
getDom: function () {
- var wrapper = document.createElement("div");
+ const wrapper = document.createElement("div");
wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line";
// get the compliment text
- var complimentText = this.randomCompliment();
+ const complimentText = this.randomCompliment();
// split it into parts on newline text
- var parts = complimentText.split("\n");
+ const parts = complimentText.split("\n");
// create a span to hold it all
- var compliment = document.createElement("span");
+ const compliment = document.createElement("span");
// process all the parts of the compliment text
- for (var part of parts) {
+ for (const part of parts) {
// create a text element for each part
compliment.appendChild(document.createTextNode(part));
// add a break `
diff --git a/modules/default/defaultmodules.js b/modules/default/defaultmodules.js
index 9bdcbe95..46bb5b87 100644
--- a/modules/default/defaultmodules.js
+++ b/modules/default/defaultmodules.js
@@ -1,13 +1,10 @@
-/* Magic Mirror
- * Default Modules List
+/* Magic Mirror Default Modules List
+ * Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
-
-// Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
-
-var defaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification", "weather"];
+const defaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification", "weather"];
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
diff --git a/modules/default/newsfeed/newsfeed.js b/modules/default/newsfeed/newsfeed.js
index f33fd4cd..38f48dc6 100644
--- a/modules/default/newsfeed/newsfeed.js
+++ b/modules/default/newsfeed/newsfeed.js
@@ -90,8 +90,8 @@ Module.register("newsfeed", {
this.loaded = true;
this.error = null;
- } else if (notification === "INCORRECT_URL") {
- this.error = `Incorrect url: ${payload.url}`;
+ } else if (notification === "NEWSFEED_ERROR") {
+ this.error = this.translate(payload.error_type);
this.scheduleUpdateInterval();
}
},
@@ -189,9 +189,9 @@ Module.register("newsfeed", {
}
if (this.config.prohibitedWords.length > 0) {
- newsItems = newsItems.filter(function (value) {
+ newsItems = newsItems.filter(function (item) {
for (let word of this.config.prohibitedWords) {
- if (value["title"].toLowerCase().indexOf(word.toLowerCase()) > -1) {
+ if (item.title.toLowerCase().indexOf(word.toLowerCase()) > -1) {
return false;
}
}
diff --git a/modules/default/newsfeed/newsfeed.njk b/modules/default/newsfeed/newsfeed.njk
index 39062721..cf1b366f 100644
--- a/modules/default/newsfeed/newsfeed.njk
+++ b/modules/default/newsfeed/newsfeed.njk
@@ -3,45 +3,47 @@
{% for item in items %}
- {% if (config.showSourceTitle and item.sourceTitle) or config.showPublishDate %}
-
- {% if item.sourceTitle and config.showSourceTitle %}
- {{ item.sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %}
- {% endif %}
- {% if config.showPublishDate %}
- {{ item.publishDate }}:
- {% endif %}
-
- https://rodrigoramirez.com/qpanel-0-13-0/feed/
- 3
-
-
- Problema VirtualBox “starting virtual machine” …
- https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/
- https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/#respond
- Sat, 10 Sep 2016 22:50:13 +0000
-
-
-
-
-
+ https://rodrigoramirez.com/qpanel-0-13-0/feed/
+ 3
+
+
+ Problema VirtualBox “starting virtual machine” …
+ https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/
+ https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/#respond
+ Sat, 10 Sep 2016 22:50:13 +0000
+
+
+
+
+
- https://rodrigoramirez.com/?p=1284
- Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox. La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje “starting virtual machine”, como el de la imagen de a continuación. […]
+ https://rodrigoramirez.com/?p=1284
+ Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox. La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje “starting virtual machine”, como el de la imagen de a continuación. […]
]]>
- Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox. La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje “starting virtual machine”, como el de la imagen de a continuación.
+ Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox. La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje “starting virtual machine”, como el de la imagen de a continuación.
Ninguna, pero ninguna maquina arrancó, se quedaban en ese mensaje. Fue de esos instantes en que sudas helado …
Con un poco de investigación fue a parar al archivo ~/.VirtualBox/VBoxSVC.log que indicaba
@@ -85,7 +85,7 @@
Fui… algo de donde agarrarse. Mirando un poco mas se trataba de problemas con los permisos al vboxdrvu, mirando indicaba que tenía 0600.
]]>
- https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/feed/
- 0
-
-
- Mejorando la consola interactiva de Python
- https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/
- https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/#comments
- Tue, 06 Sep 2016 04:24:43 +0000
-
-
-
-
+ https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/feed/
+ 0
+
+
+ Mejorando la consola interactiva de Python
+ https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/
+ https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/#comments
+ Tue, 06 Sep 2016 04:24:43 +0000
+
+
+
+
- https://rodrigoramirez.com/?p=1247
- Cuando estás desarrollando en Python es muy cool estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente. La consola de Python funciona y cumple su cometido. Solo al tipear python te permite entrar en modo interactivo e ir probando cosas. El punto es que a veces […]
+ https://rodrigoramirez.com/?p=1247
+ Cuando estás desarrollando en Python es muy cool estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente. La consola de Python funciona y cumple su cometido. Solo al tipear python te permite entrar en modo interactivo e ir probando cosas. El punto es que a veces […]
]]>
- Cuando estás desarrollando en Python es muy cool estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente.
+ Cuando estás desarrollando en Python es muy cool estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente.
La consola de Python funciona y cumple su cometido. Solo al tipear python te permite entrar en modo interactivo e ir probando cosas.
El punto es que a veces uno necesita ir un poco más allá. Como autocomentado de código o resaltado de sintaxis, para eso tengo dos truco que utilizo generalmente.
Truco a)
@@ -139,31 +139,31 @@ $ ls -lh /dev/vboxdrvu
O lo agregas a un bashrc, zshrc o la shell que ocupes.
]]>
- https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/feed/
- 4
-
-
- QPanel 0.12.0 con estadísticas
- https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/
- https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/#respond
- Mon, 22 Aug 2016 04:19:03 +0000
-
-
-
-
-
-
-
-
-
-
-
+ https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/feed/
+ 4
+
+
+ QPanel 0.12.0 con estadísticas
+ https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/
+ https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/#respond
+ Mon, 22 Aug 2016 04:19:03 +0000
+
+
+
+
+
+
+
+
+
+
+
- https://rodrigoramirez.com/?p=1268
- Ya está disponible una nueva versión de QPanel, esta es la 0.12.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.12.0 En esta nueva versión las funcionalidades agregadas son: Permite remover los agentes de las cola Posibilidad de cancelar llamadas que están en espera de atención Estadísticas por rango de fecha obtenidas desde […]
+ https://rodrigoramirez.com/?p=1268
+ Ya está disponible una nueva versión de QPanel, esta es la 0.12.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.12.0 En esta nueva versión las funcionalidades agregadas son: Permite remover los agentes de las cola Posibilidad de cancelar llamadas que están en espera de atención Estadísticas por rango de fecha obtenidas desde […]
- https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/feed/
- 0
-
-
- QPanel 0.11.0 con Spy, Whisper y mas
- https://rodrigoramirez.com/qpanel-spy-supervisor/
- https://rodrigoramirez.com/qpanel-spy-supervisor/#comments
- Thu, 21 Jul 2016 01:53:21 +0000
-
-
-
-
-
-
-
-
-
-
-
+ https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/feed/
+ 0
+
+
+ QPanel 0.11.0 con Spy, Whisper y mas
+ https://rodrigoramirez.com/qpanel-spy-supervisor/
+ https://rodrigoramirez.com/qpanel-spy-supervisor/#comments
+ Thu, 21 Jul 2016 01:53:21 +0000
+
+
+
+
+
+
+
+
+
+
+
- https://rodrigoramirez.com/?p=1245
- Ya está disponible una nueva versión de QPanel, esta es la 0.11.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.11.0 Esta versión hemos agregado algunas funcionalidades que los usuarios han ido solicitando. Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que […]
+ https://rodrigoramirez.com/?p=1245
+ Ya está disponible una nueva versión de QPanel, esta es la 0.11.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.11.0 Esta versión hemos agregado algunas funcionalidades que los usuarios han ido solicitando. Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que […]
- https://rodrigoramirez.com/qpanel-spy-supervisor/feed/
- 4
-
-
- Añadir Swap a un sistema
- https://rodrigoramirez.com/crear-swap/
- https://rodrigoramirez.com/crear-swap/#respond
- Fri, 15 Jul 2016 05:07:43 +0000
-
-
+ https://rodrigoramirez.com/qpanel-spy-supervisor/feed/
+ 4
+
+
+ Añadir Swap a un sistema
+ https://rodrigoramirez.com/crear-swap/
+ https://rodrigoramirez.com/crear-swap/#respond
+ Fri, 15 Jul 2016 05:07:43 +0000
+
+
- https://rodrigoramirez.com/?p=1234
- Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap. La memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM. El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto […]
+ https://rodrigoramirez.com/?p=1234
+ Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap. La memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM. El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto […]
]]>
- Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap.
+ Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap.
La memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM.
El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto un espacio para la Swap, lo que te lleva a que el sistema pueda tener crash durante la ejecución.
Para comprobar la asignación de memoria, al ejecutar el comando free nos debería mostrar como algo similar a lo siguiente
]]>
- https://rodrigoramirez.com/crear-swap/feed/
- 0
-
-
- QPanel 0.10.0 con vista consolidada
- https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/
- https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/#respond
- Mon, 20 Jun 2016 19:32:55 +0000
-
-
-
-
-
-
-
+ https://rodrigoramirez.com/crear-swap/feed/
+ 0
+
+
+ QPanel 0.10.0 con vista consolidada
+ https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/
+ https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/#respond
+ Mon, 20 Jun 2016 19:32:55 +0000
+
+
+
+
+
+
+
- https://rodrigoramirez.com/?p=1227
- Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible. Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.10.0 Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad. La nueva funcionalidad incluida es que ahora es posible contar con una vista consolidada para […]
+ https://rodrigoramirez.com/?p=1227
+ Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible. Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.10.0 Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad. La nueva funcionalidad incluida es que ahora es posible contar con una vista consolidada para […]
]]>
- Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible.
+ Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible.
Para instalar esta nueva versión, debes visitar la siguiente URL
- https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/feed/
- 0
-
-
- Nerdearla 2016, WebRTC Glue
- https://rodrigoramirez.com/nerdearla-2016/
- https://rodrigoramirez.com/nerdearla-2016/#respond
- Wed, 15 Jun 2016 17:55:41 +0000
-
-
-
-
-
-
-
-
-
+ https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/feed/
+ 0
+
+
+ Nerdearla 2016, WebRTC Glue
+ https://rodrigoramirez.com/nerdearla-2016/
+ https://rodrigoramirez.com/nerdearla-2016/#respond
+ Wed, 15 Jun 2016 17:55:41 +0000
+
+
+
+
+
+
+
+
+
- https://rodrigoramirez.com/?p=1218
- Días atrás estuve participando en el evento llamado Nerdearla en Buenos Aires. El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes. Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te […]
+ https://rodrigoramirez.com/?p=1218
+ Días atrás estuve participando en el evento llamado Nerdearla en Buenos Aires. El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes. Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te […]
]]>
- Días atrás estuve participando en el evento llamado Nerdearla en Buenos Aires. El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes.
+ Días atrás estuve participando en el evento llamado Nerdearla en Buenos Aires. El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes.
Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te lo perdiste te recomiendo que estés pendiente para el proximo año.
]]>
- https://rodrigoramirez.com/nerdearla-2016/feed/
- 0
-
-
- QPanel 0.9.0
- https://rodrigoramirez.com/qpanel-0-9-0/
- https://rodrigoramirez.com/qpanel-0-9-0/#respond
- Mon, 09 May 2016 18:40:23 +0000
-
-
-
-
-
-
-
-
-
-
+ https://rodrigoramirez.com/nerdearla-2016/feed/
+ 0
+
+
+ QPanel 0.9.0
+ https://rodrigoramirez.com/qpanel-0-9-0/
+ https://rodrigoramirez.com/qpanel-0-9-0/#respond
+ Mon, 09 May 2016 18:40:23 +0000
+
+
+
+
+
+
+
+
+
+
- https://rodrigoramirez.com/?p=1206
- El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.9.0 Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de […]
+ https://rodrigoramirez.com/?p=1206
+ El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.9.0 Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de […]
]]>
- El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0
+ El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0
Para instalar esta nueva versión, debes visitar la siguiente URL
- https://rodrigoramirez.com/qpanel-0-9-0/feed/
- 0
-
-
- Mandar un email desde la shell
- https://rodrigoramirez.com/mandar-un-email-desde-la-shell/
- https://rodrigoramirez.com/mandar-un-email-desde-la-shell/#comments
- Wed, 13 Apr 2016 13:05:13 +0000
-
-
-
-
-
-
-
-
-
+ https://rodrigoramirez.com/qpanel-0-9-0/feed/
+ 0
+
+
+ Mandar un email desde la shell
+ https://rodrigoramirez.com/mandar-un-email-desde-la-shell/
+ https://rodrigoramirez.com/mandar-un-email-desde-la-shell/#comments
+ Wed, 13 Apr 2016 13:05:13 +0000
+
+
+
+
+
+
+
+
+
- https://rodrigoramirez.com/?p=1172
- Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando mail en un servidor con Linux. Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un […]
+ https://rodrigoramirez.com/?p=1172
+ Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando mail en un servidor con Linux. Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un […]
]]>
- Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando mail en un servidor con Linux.
+ Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando mail en un servidor con Linux.
Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un echo le pasas por pipe a mail
echo "Cuerpo del mensaje" | mail -s Asunto a@rodrigoramirez.com