mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-08-21 12:55:22 +00:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
@@ -3,4 +3,3 @@ vendor/*
|
|||||||
!/modules/default/**
|
!/modules/default/**
|
||||||
!/modules/node_helper
|
!/modules/node_helper
|
||||||
!/modules/node_helper/**
|
!/modules/node_helper/**
|
||||||
!/modules/default/defaultmodules.js
|
|
||||||
|
@@ -2,9 +2,11 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"indent": ["error", "tab"],
|
"indent": ["error", "tab"],
|
||||||
"quotes": ["error", "double"],
|
"quotes": ["error", "double"],
|
||||||
|
"semi": ["error"],
|
||||||
"max-len": ["error", 250],
|
"max-len": ["error", 250],
|
||||||
"curly": "error",
|
"curly": "error",
|
||||||
"camelcase": ["error", {"properties": "never"}],
|
"camelcase": ["error", {"properties": "never"}],
|
||||||
|
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
|
||||||
"no-trailing-spaces": ["error", {"ignoreComments": false }],
|
"no-trailing-spaces": ["error", {"ignoreComments": false }],
|
||||||
"no-irregular-whitespace": ["error"]
|
"no-irregular-whitespace": ["error"]
|
||||||
},
|
},
|
||||||
|
14
CHANGELOG.md
Normal file → Executable file
14
CHANGELOG.md
Normal file → Executable file
@@ -17,6 +17,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Russian translation for “Feels”
|
- Russian translation for “Feels”
|
||||||
- Calendar module: added `nextDaysRelative` config option
|
- Calendar module: added `nextDaysRelative` config option
|
||||||
- Add `broadcastPastEvents` config option for calendars to include events from the past `maximumNumberOfDays` in event broadcasts
|
- Add `broadcastPastEvents` config option for calendars to include events from the past `maximumNumberOfDays` in event broadcasts
|
||||||
|
- Added feature to broadcast news feed items `NEWS_FEED` and updated news items `NEWS_FEED_UPDATED` in default [newsfeed](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/newsfeed) module (when news is updated) with documented default and `config.js` options in [README.md](https://github.com/MichMich/MagicMirror/blob/develop/modules/default/newsfeed/README.md)
|
||||||
|
- Added notifications to default `clock` module broadcasting `CLOCK_SECOND` and `CLOCK_MINUTE` for the respective time elapsed.
|
||||||
|
- Added UK Met Office Datapoint feed as a provider in the default weather module.
|
||||||
|
- added new provider class
|
||||||
|
- added suncalc.js dependency to calculate sun times (not provided in UK Met Office feed)
|
||||||
|
- added "tempUnits" and "windUnits" to allow, for example, temp in metric (i.e. celsius) and wind in imperial (i.e. mph). These will override "units" if specified, otherwise the "units" value will be used.
|
||||||
|
- use Feels Like temp from feed if present
|
||||||
|
- optionally display probability of precipitation (PoP) in current weather (UK Met Office data)
|
||||||
|
- automatically try to fix eslint errors by passing `--fix` option to it
|
||||||
|
|
||||||
### Updated
|
### Updated
|
||||||
- English translation for "Feels" to "Feels like"
|
- English translation for "Feels" to "Feels like"
|
||||||
@@ -26,9 +35,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Only update clock once per minute when seconds aren't shown
|
- Only update clock once per minute when seconds aren't shown
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- fixed issue [#1696](https://github.com/MichMich/MagicMirror/issues/1696), some ical files start date to not parse to date type
|
||||||
- Allowance HTML5 autoplay-policy (policy is changed from Chrome 66 updates)
|
- Allowance HTML5 autoplay-policy (policy is changed from Chrome 66 updates)
|
||||||
- Handle SIGTERM messages
|
- Handle SIGTERM messages
|
||||||
- Fixes sliceMultiDayEvents so it respects maximumNumberOfDays
|
- Fixes sliceMultiDayEvents so it respects maximumNumberOfDays
|
||||||
|
- Minor types in default NewsFeed [README.md](https://github.com/MichMich/MagicMirror/blob/develop/modules/default/newsfeed/README.md)
|
||||||
|
- Fix typos and small syntax errors, cleanup dependencies, remove multiple-empty-lines, add semi-rule
|
||||||
|
- Fixed issues with calendar not displaying one-time changes to repeating events
|
||||||
|
- Updated the fetchedLocationName variable in currentweather.js so that city shows up in the header
|
||||||
|
|
||||||
## [2.7.1] - 2019-04-02
|
## [2.7.1] - 2019-04-02
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ module.exports = function(grunt) {
|
|||||||
pkg: grunt.file.readJSON("package.json"),
|
pkg: grunt.file.readJSON("package.json"),
|
||||||
eslint: {
|
eslint: {
|
||||||
options: {
|
options: {
|
||||||
|
fix: "true",
|
||||||
configFile: ".eslintrc.json"
|
configFile: ".eslintrc.json"
|
||||||
},
|
},
|
||||||
target: [
|
target: [
|
||||||
@@ -26,7 +27,7 @@ module.exports = function(grunt) {
|
|||||||
stylelint: {
|
stylelint: {
|
||||||
simple: {
|
simple: {
|
||||||
options: {
|
options: {
|
||||||
configFile: ".stylelintrc"
|
configFile: ".stylelintrc.json"
|
||||||
},
|
},
|
||||||
src: [
|
src: [
|
||||||
"css/main.css",
|
"css/main.css",
|
||||||
@@ -42,11 +43,11 @@ module.exports = function(grunt) {
|
|||||||
src: [
|
src: [
|
||||||
"package.json",
|
"package.json",
|
||||||
".eslintrc.json",
|
".eslintrc.json",
|
||||||
".stylelintrc",
|
".stylelintrc.json",
|
||||||
|
"installers/pm2_MagicMirror.json",
|
||||||
"translations/*.json",
|
"translations/*.json",
|
||||||
"modules/default/*/translations/*.json",
|
"modules/default/*/translations/*.json",
|
||||||
"installers/pm2_MagicMirror.json",
|
"vendor/package.json"
|
||||||
"vendor/package.js"
|
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
reporter: "jshint"
|
reporter: "jshint"
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
|
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
|
||||||
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a>
|
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a>
|
||||||
<a href="http://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
<a href="http://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
||||||
<a href="https://travis-ci.org/MichMich/MagicMirror"><img src="https://travis-ci.org/MichMich/MagicMirror.svg" alt="Travis"></a>
|
<a href="https://travis-ci.com/MichMich/MagicMirror"><img src="https://travis-ci.com/MichMich/MagicMirror.svg" alt="Travis"></a>
|
||||||
<a href="https://snyk.io/test/github/MichMich/MagicMirror"><img src="https://snyk.io/test/github/MichMich/MagicMirror/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/MichMich/MagicMirror" style="max-width:100%;"></a>
|
<a href="https://snyk.io/test/github/MichMich/MagicMirror"><img src="https://snyk.io/test/github/MichMich/MagicMirror/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/MichMich/MagicMirror" style="max-width:100%;"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ Thanks for your help in making MagicMirror² better!
|
|||||||
MagicMirror² is opensource and free. That doesn't mean we don't need any money.
|
MagicMirror² is opensource and free. That doesn't mean we don't need any money.
|
||||||
|
|
||||||
Please consider a donation to help us cover the ongoing costs like webservers and email services.
|
Please consider a donation to help us cover the ongoing costs like webservers and email services.
|
||||||
If we recieve enough donations we might even be able to free up some working hours and spend some extra time improving the MagicMirror² core.
|
If we receive enough donations we might even be able to free up some working hours and spend some extra time improving the MagicMirror² core.
|
||||||
|
|
||||||
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
|
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Use seperate scope to prevent global scope pollution
|
// Use separate scope to prevent global scope pollution
|
||||||
(function () {
|
(function () {
|
||||||
var config = {};
|
var config = {};
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
// Prefer command line arguments over environment variables
|
// Prefer command line arguments over environment variables
|
||||||
["address", "port"].forEach((key) => {
|
["address", "port"].forEach((key) => {
|
||||||
config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]);
|
config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getServerConfig(url) {
|
function getServerConfig(url) {
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
const request = lib.get(url, (response) => {
|
const request = lib.get(url, (response) => {
|
||||||
var configData = "";
|
var configData = "";
|
||||||
|
|
||||||
// Gather incomming data
|
// Gather incoming data
|
||||||
response.on("data", function(chunk) {
|
response.on("data", function(chunk) {
|
||||||
configData += chunk;
|
configData += chunk;
|
||||||
});
|
});
|
||||||
@@ -43,8 +43,8 @@
|
|||||||
request.on("error", function(error) {
|
request.on("error", function(error) {
|
||||||
reject(new Error(`Unable to read config from server (${url} (${error.message}`));
|
reject(new Error(`Unable to read config from server (${url} (${error.message}`));
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
function fail(message, code = 1) {
|
function fail(message, code = 1) {
|
||||||
if (message !== undefined && typeof message === "string") {
|
if (message !== undefined && typeof message === "string") {
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
child.on("close", (code) => {
|
child.on("close", (code) => {
|
||||||
if (code != 0) {
|
if (code !== 0) {
|
||||||
console.log(`There something wrong. The clientonly is not running code ${code}`);
|
console.log(`There something wrong. The clientonly is not running code ${code}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -83,7 +83,9 @@ var config = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
showSourceTitle: true,
|
showSourceTitle: true,
|
||||||
showPublishDate: true
|
showPublishDate: true,
|
||||||
|
broadcastNewsFeeds: true,
|
||||||
|
broadcastNewsUpdates: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { danger, fail, warn } from "danger"
|
import { danger, fail, warn } from "danger";
|
||||||
|
|
||||||
// Check if the CHANGELOG.md file has been edited
|
// Check if the CHANGELOG.md file has been edited
|
||||||
// Fail the build and post a comment reminding submitters to do so if it wasn't changed
|
// Fail the build and post a comment reminding submitters to do so if it wasn't changed
|
||||||
if (!danger.git.modified_files.includes("CHANGELOG.md")) {
|
if (!danger.git.modified_files.includes("CHANGELOG.md")) {
|
||||||
warn("Please include an updated `CHANGELOG.md` file.<br>This way we can keep track of all the contributions.")
|
warn("Please include an updated `CHANGELOG.md` file.<br>This way we can keep track of all the contributions.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the PR request is send to the master branch.
|
// Check if the PR request is send to the master branch.
|
||||||
@@ -12,6 +12,6 @@ if (danger.github.pr.base.ref === "master" && danger.github.pr.user.login !== "M
|
|||||||
// Check if the PR body or title includes the text: #accepted.
|
// Check if the PR body or title includes the text: #accepted.
|
||||||
// If not, the PR will fail.
|
// If not, the PR will fail.
|
||||||
if ((danger.github.pr.body + danger.github.pr.title).includes("#accepted")) {
|
if ((danger.github.pr.body + danger.github.pr.title).includes("#accepted")) {
|
||||||
fail("Please send all your pull requests to the `develop` branch.<br>Pull requests on the `master` branch will not be accepted.")
|
fail("Please send all your pull requests to the `develop` branch.<br>Pull requests on the `master` branch will not be accepted.");
|
||||||
}
|
}
|
||||||
}
|
}
|
10
js/app.js
10
js/app.js
@@ -48,7 +48,6 @@ var App = function() {
|
|||||||
*
|
*
|
||||||
* argument callback function - The callback function.
|
* argument callback function - The callback function.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var loadConfig = function(callback) {
|
var loadConfig = function(callback) {
|
||||||
console.log("Loading config ...");
|
console.log("Loading config ...");
|
||||||
var defaults = require(__dirname + "/defaults.js");
|
var defaults = require(__dirname + "/defaults.js");
|
||||||
@@ -67,7 +66,7 @@ var App = function() {
|
|||||||
var config = Object.assign(defaults, c);
|
var config = Object.assign(defaults, c);
|
||||||
callback(config);
|
callback(config);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code == "ENOENT") {
|
if (e.code === "ENOENT") {
|
||||||
console.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration."));
|
console.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration."));
|
||||||
} else if (e instanceof ReferenceError || e instanceof SyntaxError) {
|
} else if (e instanceof ReferenceError || e instanceof SyntaxError) {
|
||||||
console.error(Utils.colors.error("WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: " + e.stack));
|
console.error(Utils.colors.error("WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: " + e.stack));
|
||||||
@@ -96,7 +95,7 @@ var App = function() {
|
|||||||
". Check README and CHANGELOG for more up-to-date ways of getting the same functionality.")
|
". Check README and CHANGELOG for more up-to-date ways of getting the same functionality.")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/* loadModule(module)
|
/* loadModule(module)
|
||||||
* Loads a specific module.
|
* Loads a specific module.
|
||||||
@@ -173,7 +172,7 @@ var App = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* cmpVersions(a,b)
|
/* cmpVersions(a,b)
|
||||||
* Compare two symantic version numbers and return the difference.
|
* Compare two semantic version numbers and return the difference.
|
||||||
*
|
*
|
||||||
* argument a string - Version number a.
|
* argument a string - Version number a.
|
||||||
* argument a string - Version number b.
|
* argument a string - Version number b.
|
||||||
@@ -197,7 +196,7 @@ var App = function() {
|
|||||||
/* start(callback)
|
/* start(callback)
|
||||||
* This methods starts the core app.
|
* This methods starts the core app.
|
||||||
* It loads the config, then it loads all modules.
|
* It loads the config, then it loads all modules.
|
||||||
* When it"s done it executs the callback with the config as argument.
|
* When it's done it executes the callback with the config as argument.
|
||||||
*
|
*
|
||||||
* argument callback function - The callback function.
|
* argument callback function - The callback function.
|
||||||
*/
|
*/
|
||||||
@@ -231,7 +230,6 @@ var App = function() {
|
|||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") {
|
||||||
callback(config);
|
callback(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
var prototype = new this();
|
var prototype = new this();
|
||||||
initializing = false;
|
initializing = false;
|
||||||
|
|
||||||
// Make a copy of all prototype properies, to prevent reference issues.
|
// Make a copy of all prototype properties, to prevent reference issues.
|
||||||
for (var name in prototype) {
|
for (var name in prototype) {
|
||||||
prototype[name] = cloneObject(prototype[name]);
|
prototype[name] = cloneObject(prototype[name]);
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
// Copy the properties over onto the new prototype
|
// Copy the properties over onto the new prototype
|
||||||
for (var name in prop) {
|
for (var name in prop) {
|
||||||
// Check if we're overwriting an existing function
|
// Check if we're overwriting an existing function
|
||||||
prototype[name] = typeof prop[name] == "function" &&
|
prototype[name] = typeof prop[name] === "function" &&
|
||||||
typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function (name, fn) {
|
typeof _super[name] === "function" && fnTest.test(prop[name]) ? (function (name, fn) {
|
||||||
return function () {
|
return function () {
|
||||||
var tmp = this._super;
|
var tmp = this._super;
|
||||||
|
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
var ret = fn.apply(this, arguments);
|
var ret = fn.apply(this, arguments);
|
||||||
this._super = tmp;
|
this._super = tmp;
|
||||||
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
})(name, prop[name]) : prop[name];
|
})(name, prop[name]) : prop[name];
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
var Loader = (function() {
|
var Loader = (function() {
|
||||||
|
|
||||||
/* Create helper valiables */
|
/* Create helper variables */
|
||||||
|
|
||||||
var loadedModuleFiles = [];
|
var loadedModuleFiles = [];
|
||||||
var loadedFiles = [];
|
var loadedFiles = [];
|
||||||
@@ -55,7 +55,7 @@ var Loader = (function() {
|
|||||||
module.start();
|
module.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifiy core of loded modules.
|
// Notify core of loaded modules.
|
||||||
MM.modulesStarted(moduleObjects);
|
MM.modulesStarted(moduleObjects);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,7 +104,6 @@ var Loader = (function() {
|
|||||||
config: moduleData.config,
|
config: moduleData.config,
|
||||||
classes: (typeof moduleData.classes !== "undefined") ? moduleData.classes + " " + module : module
|
classes: (typeof moduleData.classes !== "undefined") ? moduleData.classes + " " + module : module
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return moduleFiles;
|
return moduleFiles;
|
||||||
@@ -138,7 +137,6 @@ var Loader = (function() {
|
|||||||
afterLoad();
|
afterLoad();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* bootstrapModule(module, mObj)
|
/* bootstrapModule(module, mObj)
|
||||||
@@ -164,7 +162,6 @@ var Loader = (function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* loadFile(fileName)
|
/* loadFile(fileName)
|
||||||
@@ -210,7 +207,6 @@ var Loader = (function() {
|
|||||||
document.getElementsByTagName("head")[0].appendChild(stylesheet);
|
document.getElementsByTagName("head")[0].appendChild(stylesheet);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Public Methods */
|
/* Public Methods */
|
||||||
@@ -261,5 +257,4 @@ var Loader = (function() {
|
|||||||
loadFile(module.file(fileName), callback);
|
loadFile(module.file(fileName), callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
11
js/main.js
11
js/main.js
@@ -292,7 +292,7 @@ var MM = (function() {
|
|||||||
var moduleWrapper = document.getElementById(module.identifier);
|
var moduleWrapper = document.getElementById(module.identifier);
|
||||||
if (moduleWrapper !== null) {
|
if (moduleWrapper !== null) {
|
||||||
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
|
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
|
||||||
// Restore the postition. See hideModule() for more info.
|
// Restore the position. See hideModule() for more info.
|
||||||
moduleWrapper.style.position = "static";
|
moduleWrapper.style.position = "static";
|
||||||
|
|
||||||
updateWrapperStates();
|
updateWrapperStates();
|
||||||
@@ -312,7 +312,7 @@ var MM = (function() {
|
|||||||
/* updateWrapperStates()
|
/* 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 schould be called by the show and hide methods.
|
* This method should be called by the show and hide methods.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* If the top_bar only contains the update notification. And no update is available,
|
* If the top_bar only contains the update notification. And no update is available,
|
||||||
@@ -320,7 +320,6 @@ var MM = (function() {
|
|||||||
* an ugly top margin. By using this function, the top bar will be hidden if the
|
* an ugly top margin. By using this function, the top bar will be hidden if the
|
||||||
* update notification is not visible.
|
* update notification is not visible.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var updateWrapperStates = function() {
|
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"];
|
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"];
|
||||||
|
|
||||||
@@ -330,7 +329,7 @@ var MM = (function() {
|
|||||||
|
|
||||||
var showWrapper = false;
|
var showWrapper = false;
|
||||||
Array.prototype.forEach.call(moduleWrappers, function(moduleWrapper) {
|
Array.prototype.forEach.call(moduleWrappers, function(moduleWrapper) {
|
||||||
if (moduleWrapper.style.position == "" || moduleWrapper.style.position == "static") {
|
if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") {
|
||||||
showWrapper = true;
|
showWrapper = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -479,7 +478,7 @@ var MM = (function() {
|
|||||||
/* sendNotification(notification, payload, sender)
|
/* sendNotification(notification, payload, sender)
|
||||||
* Send a notification to all modules.
|
* Send a notification to all modules.
|
||||||
*
|
*
|
||||||
* argument notification string - The identifier of the noitication.
|
* argument notification string - The identifier of the notification.
|
||||||
* argument payload mixed - The payload of the notification.
|
* argument payload mixed - The payload of the notification.
|
||||||
* argument sender Module - The module that sent the notification.
|
* argument sender Module - The module that sent the notification.
|
||||||
*/
|
*/
|
||||||
@@ -559,7 +558,7 @@ var MM = (function() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
// Add polyfill for Object.assign.
|
// Add polyfill for Object.assign.
|
||||||
if (typeof Object.assign != "function") {
|
if (typeof Object.assign !== "function") {
|
||||||
(function() {
|
(function() {
|
||||||
Object.assign = function(target) {
|
Object.assign = function(target) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
17
js/module.js
17
js/module.js
@@ -76,7 +76,7 @@ var Module = Class.extend({
|
|||||||
/* getDom()
|
/* getDom()
|
||||||
* This method generates the dom which needs to be displayed. This method is called by the Magic Mirror core.
|
* This method 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 getTemplete method could be subclassed.
|
* Alternatively, the getTemplate method could be subclassed.
|
||||||
*
|
*
|
||||||
* return DomObject | Promise - The dom or a promise with the dom to display.
|
* return DomObject | Promise - The dom or a promise with the dom to display.
|
||||||
*/
|
*/
|
||||||
@@ -92,7 +92,7 @@ var Module = Class.extend({
|
|||||||
// the template is a filename
|
// the template is a filename
|
||||||
self.nunjucksEnvironment().render(template, templateData, function (err, res) {
|
self.nunjucksEnvironment().render(template, templateData, function (err, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
Log.error(err)
|
Log.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.innerHTML = res;
|
div.innerHTML = res;
|
||||||
@@ -121,7 +121,7 @@ var Module = Class.extend({
|
|||||||
|
|
||||||
/* getTemplate()
|
/* getTemplate()
|
||||||
* This method returns the template for the module which is used by the default getDom implementation.
|
* This method 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 tempate.
|
* 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.
|
||||||
*
|
*
|
||||||
@@ -138,7 +138,7 @@ var Module = Class.extend({
|
|||||||
* return Object
|
* return Object
|
||||||
*/
|
*/
|
||||||
getTemplateData: function () {
|
getTemplateData: function () {
|
||||||
return {}
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
/* notificationReceived(notification, payload, sender)
|
/* notificationReceived(notification, payload, sender)
|
||||||
@@ -164,7 +164,7 @@ var Module = Class.extend({
|
|||||||
* @returns Nunjucks Environment
|
* @returns Nunjucks Environment
|
||||||
*/
|
*/
|
||||||
nunjucksEnvironment: function() {
|
nunjucksEnvironment: function() {
|
||||||
if (this._nunjucksEnvironment != null) {
|
if (this._nunjucksEnvironment !== null) {
|
||||||
return this._nunjucksEnvironment;
|
return this._nunjucksEnvironment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ var Module = Class.extend({
|
|||||||
lstripBlocks: true
|
lstripBlocks: true
|
||||||
});
|
});
|
||||||
this._nunjucksEnvironment.addFilter("translate", function(str) {
|
this._nunjucksEnvironment.addFilter("translate", function(str) {
|
||||||
return self.translate(str)
|
return self.translate(str);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this._nunjucksEnvironment;
|
return this._nunjucksEnvironment;
|
||||||
@@ -233,7 +233,7 @@ var Module = Class.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
/* socket()
|
/* 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.
|
||||||
*/
|
*/
|
||||||
socket: function () {
|
socket: function () {
|
||||||
@@ -438,11 +438,10 @@ Module.create = function (name) {
|
|||||||
var ModuleClass = Module.extend(clonedDefinition);
|
var ModuleClass = Module.extend(clonedDefinition);
|
||||||
|
|
||||||
return new ModuleClass();
|
return new ModuleClass();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* cmpVersions(a,b)
|
/* cmpVersions(a,b)
|
||||||
* Compare two symantic version numbers and return the difference.
|
* Compare two semantic version numbers and return the difference.
|
||||||
*
|
*
|
||||||
* argument a string - Version number a.
|
* argument a string - Version number a.
|
||||||
* argument a string - Version number b.
|
* argument a string - Version number b.
|
||||||
|
@@ -26,8 +26,8 @@ var Server = function(config, callback) {
|
|||||||
|
|
||||||
server.listen(port, config.address ? config.address : null);
|
server.listen(port, config.address ? config.address : null);
|
||||||
|
|
||||||
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length == 0) {
|
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
|
||||||
console.info(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"))
|
console.info(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(function(req, res, next) {
|
app.use(function(req, res, next) {
|
||||||
|
@@ -18,7 +18,7 @@ var Translator = (function() {
|
|||||||
xhr.overrideMimeType("application/json");
|
xhr.overrideMimeType("application/json");
|
||||||
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(stripComments(xhr.responseText)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -159,6 +159,7 @@ var Translator = (function() {
|
|||||||
|
|
||||||
return key;
|
return key;
|
||||||
},
|
},
|
||||||
|
|
||||||
/* load(module, file, isFallback, callback)
|
/* load(module, file, isFallback, callback)
|
||||||
* Load a translation file (json) and remember the data.
|
* Load a translation file (json) and remember the data.
|
||||||
*
|
*
|
||||||
|
@@ -35,13 +35,13 @@ Module.register("alert",{
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
show_notification: function(message) {
|
show_notification: function(message) {
|
||||||
if (this.config.effect == "slide") {this.config.effect = this.config.effect + "-" + this.config.position;}
|
if (this.config.effect === "slide") {this.config.effect = this.config.effect + "-" + this.config.position;}
|
||||||
msg = "";
|
msg = "";
|
||||||
if (message.title) {
|
if (message.title) {
|
||||||
msg += "<span class='thin dimmed medium'>" + message.title + "</span>";
|
msg += "<span class='thin dimmed medium'>" + message.title + "</span>";
|
||||||
}
|
}
|
||||||
if (message.message){
|
if (message.message){
|
||||||
if (msg != ""){
|
if (msg !== ""){
|
||||||
msg+= "<br />";
|
msg+= "<br />";
|
||||||
}
|
}
|
||||||
msg += "<span class='light bright small'>" + message.message + "</span>";
|
msg += "<span class='light bright small'>" + message.message + "</span>";
|
||||||
@@ -132,7 +132,7 @@ Module.register("alert",{
|
|||||||
if (typeof payload.type === "undefined") { payload.type = "alert"; }
|
if (typeof payload.type === "undefined") { payload.type = "alert"; }
|
||||||
if (payload.type === "alert") {
|
if (payload.type === "alert") {
|
||||||
this.show_alert(payload, sender);
|
this.show_alert(payload, sender);
|
||||||
} else if (payload.type = "notification") {
|
} else if (payload.type === "notification") {
|
||||||
this.show_notification(payload);
|
this.show_notification(payload);
|
||||||
}
|
}
|
||||||
} else if (notification === "HIDE_ALERT") {
|
} else if (notification === "HIDE_ALERT") {
|
||||||
@@ -152,5 +152,4 @@ Module.register("alert",{
|
|||||||
}
|
}
|
||||||
Log.info("Starting module: " + this.name);
|
Log.info("Starting module: " + this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -105,7 +105,7 @@ Module.register("calendar", {
|
|||||||
calendar.auth = {
|
calendar.auth = {
|
||||||
user: calendar.user,
|
user: calendar.user,
|
||||||
pass: calendar.pass
|
pass: calendar.pass
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addCalendar(calendar.url, calendar.auth, calendarConfig);
|
this.addCalendar(calendar.url, calendar.auth, calendarConfig);
|
||||||
@@ -135,6 +135,7 @@ Module.register("calendar", {
|
|||||||
}
|
}
|
||||||
} else if (notification === "FETCH_ERROR") {
|
} else if (notification === "FETCH_ERROR") {
|
||||||
Log.error("Calendar Error. Could not fetch calendar: " + payload.url);
|
Log.error("Calendar Error. Could not fetch calendar: " + payload.url);
|
||||||
|
this.loaded = true;
|
||||||
} else if (notification === "INCORRECT_URL") {
|
} else if (notification === "INCORRECT_URL") {
|
||||||
Log.error("Calendar Error. Incorrect url: " + payload.url);
|
Log.error("Calendar Error. Incorrect url: " + payload.url);
|
||||||
} else {
|
} else {
|
||||||
@@ -191,7 +192,6 @@ Module.register("calendar", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var eventWrapper = document.createElement("tr");
|
var eventWrapper = document.createElement("tr");
|
||||||
|
|
||||||
if (this.config.colored && !this.config.coloredSymbolOnly) {
|
if (this.config.colored && !this.config.coloredSymbolOnly) {
|
||||||
@@ -224,7 +224,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");
|
||||||
blankCell.innerHTML = " ";
|
blankCell.innerHTML = " ";
|
||||||
eventWrapper.appendChild(blankCell);
|
eventWrapper.appendChild(blankCell);
|
||||||
@@ -261,7 +261,7 @@ Module.register("calendar", {
|
|||||||
titleWrapper.colSpan = "2";
|
titleWrapper.colSpan = "2";
|
||||||
titleWrapper.align = "left";
|
titleWrapper.align = "left";
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
|
|
||||||
var timeClass = this.timeClassForUrl(event.url);
|
var timeClass = this.timeClassForUrl(event.url);
|
||||||
var timeWrapper = document.createElement("td");
|
var timeWrapper = document.createElement("td");
|
||||||
@@ -274,7 +274,7 @@ Module.register("calendar", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eventWrapper.appendChild(titleWrapper);
|
eventWrapper.appendChild(titleWrapper);
|
||||||
}else{
|
} else {
|
||||||
var timeWrapper = document.createElement("td");
|
var timeWrapper = document.createElement("td");
|
||||||
|
|
||||||
eventWrapper.appendChild(titleWrapper);
|
eventWrapper.appendChild(titleWrapper);
|
||||||
@@ -499,7 +499,7 @@ Module.register("calendar", {
|
|||||||
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
|
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
|
||||||
var count = 1;
|
var count = 1;
|
||||||
while (event.endDate > midnight) {
|
while (event.endDate > midnight) {
|
||||||
var thisEvent = JSON.parse(JSON.stringify(event)) // clone object
|
var thisEvent = JSON.parse(JSON.stringify(event)); // clone object
|
||||||
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < (today + 24 * 60 * 60 * 1000);
|
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < (today + 24 * 60 * 60 * 1000);
|
||||||
thisEvent.endDate = midnight;
|
thisEvent.endDate = midnight;
|
||||||
thisEvent.title += " (" + count + "/" + maxCount + ")";
|
thisEvent.title += " (" + count + "/" + maxCount + ")";
|
||||||
@@ -530,7 +530,6 @@ Module.register("calendar", {
|
|||||||
return events.slice(0, this.config.maximumEntries);
|
return events.slice(0, this.config.maximumEntries);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
listContainsEvent: function(eventList, event){
|
listContainsEvent: function(eventList, event){
|
||||||
for(var evt of eventList){
|
for(var evt of eventList){
|
||||||
if(evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)){
|
if(evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)){
|
||||||
@@ -538,7 +537,6 @@ Module.register("calendar", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* createEventList(url)
|
/* createEventList(url)
|
||||||
@@ -718,7 +716,6 @@ Module.register("calendar", {
|
|||||||
* Capitalize the first letter of a string
|
* Capitalize the first letter of a string
|
||||||
* Return capitalized string
|
* Return capitalized string
|
||||||
*/
|
*/
|
||||||
|
|
||||||
capFirst: function (string) {
|
capFirst: function (string) {
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
},
|
},
|
||||||
|
@@ -37,9 +37,9 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
if(auth.method === "bearer"){
|
if(auth.method === "bearer"){
|
||||||
opts.auth = {
|
opts.auth = {
|
||||||
bearer: auth.pass
|
bearer: auth.pass
|
||||||
}
|
};
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
opts.auth = {
|
opts.auth = {
|
||||||
user: auth.user,
|
user: auth.user,
|
||||||
pass: auth.pass
|
pass: auth.pass
|
||||||
@@ -47,7 +47,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
|
|
||||||
if(auth.method === "digest"){
|
if(auth.method === "digest"){
|
||||||
opts.auth.sendImmediately = false;
|
opts.auth.sendImmediately = false;
|
||||||
}else{
|
} else {
|
||||||
opts.auth.sendImmediately = true;
|
opts.auth.sendImmediately = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,8 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
// console.log(data);
|
// console.log(data);
|
||||||
newEvents = [];
|
newEvents = [];
|
||||||
|
|
||||||
var limitFunction = function(date, i) {return i < maximumEntries;};
|
// limitFunction doesn't do much limiting, see comment re: the dates array in rrule section below as to why we need to do the filtering ourselves
|
||||||
|
var limitFunction = function(date, i) {return true;};
|
||||||
|
|
||||||
var eventDate = function(event, time) {
|
var eventDate = function(event, time) {
|
||||||
return (event[time].length === 8) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
|
return (event[time].length === 8) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
|
||||||
@@ -107,7 +108,6 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// calculate the duration f the event for use with recurring events.
|
// calculate the duration f the event for use with recurring events.
|
||||||
var duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
|
var duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
|
||||||
|
|
||||||
@@ -115,12 +115,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
startDate = startDate.startOf("day");
|
startDate = startDate.startOf("day");
|
||||||
}
|
}
|
||||||
|
|
||||||
var title = "Event";
|
var title = getTitleFromEvent(event);
|
||||||
if (event.summary) {
|
|
||||||
title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
|
|
||||||
} else if(event.description) {
|
|
||||||
title = event.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
var excluded = false,
|
var excluded = false,
|
||||||
dateFilter = null;
|
dateFilter = null;
|
||||||
@@ -176,29 +171,98 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
var geo = event.geo || false;
|
var geo = event.geo || false;
|
||||||
var description = event.description || false;
|
var description = event.description || false;
|
||||||
|
|
||||||
if (typeof event.rrule != "undefined" && event.rrule != null && !isFacebookBirthday) {
|
if (typeof event.rrule !== "undefined" && event.rrule !== null && !isFacebookBirthday) {
|
||||||
var rule = event.rrule;
|
var rule = event.rrule;
|
||||||
|
var addedEvents = 0;
|
||||||
|
|
||||||
// can cause problems with e.g. birthdays before 1900
|
// can cause problems with e.g. birthdays before 1900
|
||||||
if(rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900 ||
|
if(rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900 ||
|
||||||
rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900){
|
rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900){
|
||||||
rule.origOptions.dtstart.setYear(1900);
|
rule.origOptions.dtstart.setYear(1900);
|
||||||
rule.options.dtstart.setYear(1900);
|
rule.options.dtstart.setYear(1900);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For recurring events, get the set of start dates that fall within the range
|
||||||
|
// of dates we"re looking for.
|
||||||
var dates = rule.between(past, future, true, limitFunction);
|
var dates = rule.between(past, future, true, limitFunction);
|
||||||
|
|
||||||
for (var d in dates) {
|
// The "dates" array contains the set of dates within our desired date range range that are valid
|
||||||
startDate = moment(new Date(dates[d]));
|
// for the recurrence rule. *However*, it"s possible for us to have a specific recurrence that
|
||||||
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
|
// had its date changed from outside the range to inside the range. For the time being,
|
||||||
|
// we"ll handle this by adding *all* recurrence entries into the set of dates that we check,
|
||||||
|
// because the logic below will filter out any recurrences that don"t actually belong within
|
||||||
|
// our display range.
|
||||||
|
// Would be great if there was a better way to handle this.
|
||||||
|
if (event.recurrences != undefined)
|
||||||
|
{
|
||||||
|
var pastMoment = moment(past);
|
||||||
|
var futureMoment = moment(future);
|
||||||
|
|
||||||
if (timeFilterApplies(now, endDate, dateFilter)) {
|
for (var r in event.recurrences)
|
||||||
continue;
|
{
|
||||||
|
// Only add dates that weren't already in the range we added from the rrule so that
|
||||||
|
// we don"t double-add those events.
|
||||||
|
if (moment(new Date(r)).isBetween(pastMoment, futureMoment) != true)
|
||||||
|
{
|
||||||
|
dates.push(new Date(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the set of date entries to see which recurrences should be added to our event list.
|
||||||
|
for (var d in dates) {
|
||||||
|
var date = dates[d];
|
||||||
|
// ical.js started returning recurrences and exdates as ISOStrings without time information.
|
||||||
|
// .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same
|
||||||
|
// (see https://github.com/peterbraden/ical.js/pull/84 )
|
||||||
|
var dateKey = date.toISOString().substring(0,10);
|
||||||
|
var curEvent = event;
|
||||||
|
var showRecurrence = true;
|
||||||
|
|
||||||
|
// Stop parsing this event's recurrences if we've already found maximumEntries worth of recurrences.
|
||||||
|
// (The logic below would still filter the extras, but the check is simple since we're already tracking the count)
|
||||||
|
if (addedEvents >= maximumEntries) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includePastEvents || endDate.format("x") > now) {
|
startDate = moment(date);
|
||||||
|
|
||||||
|
// For each date that we"re checking, it"s possible that there is a recurrence override for that one day.
|
||||||
|
if ((curEvent.recurrences != undefined) && (curEvent.recurrences[dateKey] != undefined))
|
||||||
|
{
|
||||||
|
// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
|
||||||
|
curEvent = curEvent.recurrences[dateKey];
|
||||||
|
startDate = moment(curEvent.start);
|
||||||
|
duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
|
||||||
|
}
|
||||||
|
// If there"s no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
|
||||||
|
else if ((curEvent.exdate != undefined) && (curEvent.exdate[dateKey] != undefined))
|
||||||
|
{
|
||||||
|
// This date is an exception date, which means we should skip it in the recurrence pattern.
|
||||||
|
showRecurrence = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
|
||||||
|
if (startDate.format("x") == endDate.format("x")) {
|
||||||
|
endDate = endDate.endOf("day");
|
||||||
|
}
|
||||||
|
|
||||||
|
var recurrenceTitle = getTitleFromEvent(curEvent);
|
||||||
|
|
||||||
|
// If this recurrence ends before the start of the date range, or starts after the end of the date range, don"t add
|
||||||
|
// it to the event list.
|
||||||
|
if (endDate.isBefore(past) || startDate.isAfter(future)) {
|
||||||
|
showRecurrence = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeFilterApplies(now, endDate, dateFilter)) {
|
||||||
|
showRecurrence = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((showRecurrence === true) && (addedEvents < maximumEntries)) {
|
||||||
|
addedEvents++;
|
||||||
newEvents.push({
|
newEvents.push({
|
||||||
title: title,
|
title: recurrenceTitle,
|
||||||
startDate: startDate.format("x"),
|
startDate: startDate.format("x"),
|
||||||
endDate: endDate.format("x"),
|
endDate: endDate.format("x"),
|
||||||
fullDayEvent: isFullDayEvent(event),
|
fullDayEvent: isFullDayEvent(event),
|
||||||
@@ -210,6 +274,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// end recurring event parsing
|
||||||
} else {
|
} else {
|
||||||
// console.log("Single event ...");
|
// console.log("Single event ...");
|
||||||
// Single event.
|
// Single event.
|
||||||
@@ -327,6 +392,24 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* getTitleFromEvent(event)
|
||||||
|
* Gets the title from the event.
|
||||||
|
*
|
||||||
|
* argument event object - The event object to check.
|
||||||
|
*
|
||||||
|
* return string - The title of the event, or "Event" if no title is found.
|
||||||
|
*/
|
||||||
|
var getTitleFromEvent = function (event) {
|
||||||
|
var title = "Event";
|
||||||
|
if (event.summary) {
|
||||||
|
title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
|
||||||
|
} else if (event.description) {
|
||||||
|
title = event.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return title;
|
||||||
|
};
|
||||||
|
|
||||||
var testTitleByFilter = function (title, filter, useRegex, regexFlags) {
|
var testTitleByFilter = function (title, filter, useRegex, regexFlags) {
|
||||||
if (useRegex) {
|
if (useRegex) {
|
||||||
// Assume if leading slash, there is also trailing slash
|
// Assume if leading slash, there is also trailing slash
|
||||||
@@ -341,7 +424,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
} else {
|
} else {
|
||||||
return title.includes(filter);
|
return title.includes(filter);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/* public methods */
|
/* public methods */
|
||||||
|
|
||||||
@@ -395,8 +478,6 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
|
|||||||
this.events = function() {
|
this.events = function() {
|
||||||
return events;
|
return events;
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = CalendarFetcher;
|
module.exports = CalendarFetcher;
|
||||||
|
@@ -60,6 +60,7 @@ module.exports = NodeHelper.create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
fetcher.onError(function(fetcher, error) {
|
fetcher.onError(function(fetcher, error) {
|
||||||
|
console.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
|
||||||
self.sendSocketNotification("FETCH_ERROR", {
|
self.sendSocketNotification("FETCH_ERROR", {
|
||||||
url: fetcher.url(),
|
url: fetcher.url(),
|
||||||
error: error
|
error: error
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "0.10"
|
- "8.9"
|
||||||
- "0.12"
|
|
||||||
- "4.2"
|
|
||||||
install: npm install
|
install: npm install
|
||||||
|
@@ -1,13 +1,16 @@
|
|||||||
var ical = require('ical')
|
'use strict';
|
||||||
, months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
||||||
|
|
||||||
|
const ical = require('ical');
|
||||||
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
|
||||||
ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function(err, data){
|
ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function (err, data) {
|
||||||
for (var k in data){
|
for (let k in data) {
|
||||||
if (data.hasOwnProperty(k)){
|
if (data.hasOwnProperty(k)) {
|
||||||
var ev = data[k]
|
var ev = data[k];
|
||||||
console.log("Conference", ev.summary, 'is in', ev.location, 'on the', ev.start.getDate(), 'of', months[ev.start.getMonth()] );
|
if (data[k].type == 'VEVENT') {
|
||||||
}
|
console.log(`${ev.summary} is in ${ev.location} on the ${ev.start.getDate()} of ${months[ev.start.getMonth()]} at ${ev.start.toLocaleTimeString('en-GB')}`);
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
118
modules/default/calendar/vendor/ical.js/example_rrule.js
vendored
Normal file
118
modules/default/calendar/vendor/ical.js/example_rrule.js
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
var ical = require('./node-ical')
|
||||||
|
var moment = require('moment')
|
||||||
|
|
||||||
|
var data = ical.parseFile('./examples/example_rrule.ics');
|
||||||
|
|
||||||
|
// Complicated example demonstrating how to handle recurrence rules and exceptions.
|
||||||
|
|
||||||
|
for (var k in data) {
|
||||||
|
|
||||||
|
// When dealing with calendar recurrences, you need a range of dates to query against,
|
||||||
|
// because otherwise you can get an infinite number of calendar events.
|
||||||
|
var rangeStart = moment("2017-01-01");
|
||||||
|
var rangeEnd = moment("2017-12-31");
|
||||||
|
|
||||||
|
|
||||||
|
var event = data[k]
|
||||||
|
if (event.type === 'VEVENT') {
|
||||||
|
|
||||||
|
var title = event.summary;
|
||||||
|
var startDate = moment(event.start);
|
||||||
|
var endDate = moment(event.end);
|
||||||
|
|
||||||
|
// Calculate the duration of the event for use with recurring events.
|
||||||
|
var duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
|
||||||
|
|
||||||
|
// Simple case - no recurrences, just print out the calendar event.
|
||||||
|
if (typeof event.rrule === 'undefined')
|
||||||
|
{
|
||||||
|
console.log('title:' + title);
|
||||||
|
console.log('startDate:' + startDate.format('MMMM Do YYYY, h:mm:ss a'));
|
||||||
|
console.log('endDate:' + endDate.format('MMMM Do YYYY, h:mm:ss a'));
|
||||||
|
console.log('duration:' + moment.duration(duration).humanize());
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complicated case - if an RRULE exists, handle multiple recurrences of the event.
|
||||||
|
else if (typeof event.rrule !== 'undefined')
|
||||||
|
{
|
||||||
|
// For recurring events, get the set of event start dates that fall within the range
|
||||||
|
// of dates we're looking for.
|
||||||
|
var dates = event.rrule.between(
|
||||||
|
rangeStart.toDate(),
|
||||||
|
rangeEnd.toDate(),
|
||||||
|
true,
|
||||||
|
function(date, i) {return true;}
|
||||||
|
)
|
||||||
|
|
||||||
|
// The "dates" array contains the set of dates within our desired date range range that are valid
|
||||||
|
// for the recurrence rule. *However*, it's possible for us to have a specific recurrence that
|
||||||
|
// had its date changed from outside the range to inside the range. One way to handle this is
|
||||||
|
// to add *all* recurrence override entries into the set of dates that we check, and then later
|
||||||
|
// filter out any recurrences that don't actually belong within our range.
|
||||||
|
if (event.recurrences != undefined)
|
||||||
|
{
|
||||||
|
for (var r in event.recurrences)
|
||||||
|
{
|
||||||
|
// Only add dates that weren't already in the range we added from the rrule so that
|
||||||
|
// we don't double-add those events.
|
||||||
|
if (moment(new Date(r)).isBetween(rangeStart, rangeEnd) != true)
|
||||||
|
{
|
||||||
|
dates.push(new Date(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the set of date entries to see which recurrences should be printed.
|
||||||
|
for(var i in dates) {
|
||||||
|
|
||||||
|
var date = dates[i];
|
||||||
|
var curEvent = event;
|
||||||
|
var showRecurrence = true;
|
||||||
|
var curDuration = duration;
|
||||||
|
|
||||||
|
startDate = moment(date);
|
||||||
|
|
||||||
|
// Use just the date of the recurrence to look up overrides and exceptions (i.e. chop off time information)
|
||||||
|
var dateLookupKey = date.toISOString().substring(0, 10);
|
||||||
|
|
||||||
|
// For each date that we're checking, it's possible that there is a recurrence override for that one day.
|
||||||
|
if ((curEvent.recurrences != undefined) && (curEvent.recurrences[dateLookupKey] != undefined))
|
||||||
|
{
|
||||||
|
// We found an override, so for this recurrence, use a potentially different title, start date, and duration.
|
||||||
|
curEvent = curEvent.recurrences[dateLookupKey];
|
||||||
|
startDate = moment(curEvent.start);
|
||||||
|
curDuration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
|
||||||
|
}
|
||||||
|
// If there's no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
|
||||||
|
else if ((curEvent.exdate != undefined) && (curEvent.exdate[dateLookupKey] != undefined))
|
||||||
|
{
|
||||||
|
// This date is an exception date, which means we should skip it in the recurrence pattern.
|
||||||
|
showRecurrence = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the the title and the end date from either the regular event or the recurrence override.
|
||||||
|
var recurrenceTitle = curEvent.summary;
|
||||||
|
endDate = moment(parseInt(startDate.format("x")) + curDuration, 'x');
|
||||||
|
|
||||||
|
// If this recurrence ends before the start of the date range, or starts after the end of the date range,
|
||||||
|
// don't process it.
|
||||||
|
if (endDate.isBefore(rangeStart) || startDate.isAfter(rangeEnd)) {
|
||||||
|
showRecurrence = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showRecurrence === true) {
|
||||||
|
|
||||||
|
console.log('title:' + recurrenceTitle);
|
||||||
|
console.log('startDate:' + startDate.format('MMMM Do YYYY, h:mm:ss a'));
|
||||||
|
console.log('endDate:' + endDate.format('MMMM Do YYYY, h:mm:ss a'));
|
||||||
|
console.log('duration:' + moment.duration(curDuration).humanize());
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
40
modules/default/calendar/vendor/ical.js/examples/example_rrule.ics
vendored
Normal file
40
modules/default/calendar/vendor/ical.js/examples/example_rrule.ics
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Google Inc//Google Calendar 70.9054//EN
|
||||||
|
VERSION:2.0
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-WR-CALNAME:ical
|
||||||
|
X-WR-TIMEZONE:US/Central
|
||||||
|
X-WR-CALDESC:
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:98765432-ABCD-DCBB-999A-987765432123
|
||||||
|
DTSTART;TZID=US/Central:20170601T090000
|
||||||
|
DTEND;TZID=US/Central:20170601T170000
|
||||||
|
DTSTAMP:20170727T044436Z
|
||||||
|
EXDATE;TZID=US/Central:20170706T090000,20170713T090000,20170720T090000,20
|
||||||
|
170803T090000
|
||||||
|
LAST-MODIFIED:20170727T044435Z
|
||||||
|
RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20170814T045959Z;BYDAY=TH
|
||||||
|
SEQUENCE:0
|
||||||
|
SUMMARY:Recurring weekly meeting from June 1 - Aug 14 (except July 6, July 13, July 20, Aug 3)
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:98765432-ABCD-DCBB-999A-987765432123
|
||||||
|
RECURRENCE-ID;TZID=US/Central:20170629T090000
|
||||||
|
DTSTART;TZID=US/Central:20170703T090000
|
||||||
|
DTEND;TZID=US/Central:20170703T120000
|
||||||
|
DTSTAMP:20170727T044436Z
|
||||||
|
LAST-MODIFIED:20170216T143445Z
|
||||||
|
SEQUENCE:0
|
||||||
|
SUMMARY:Last meeting in June moved to Monday July 3 and shortened to half day
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:12354454-ABCD-DCBB-999A-2349872354897
|
||||||
|
DTSTART;TZID=US/Central:20171201T130000
|
||||||
|
DTEND;TZID=US/Central:20171201T150000
|
||||||
|
DTSTAMP:20170727T044436Z
|
||||||
|
LAST-MODIFIED:20170727T044435Z
|
||||||
|
SEQUENCE:0
|
||||||
|
SUMMARY:Single event on Dec 1
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
@@ -6,9 +6,16 @@ exports.fromURL = function(url, opts, cb){
|
|||||||
if (!cb)
|
if (!cb)
|
||||||
return;
|
return;
|
||||||
request(url, opts, function(err, r, data){
|
request(url, opts, function(err, r, data){
|
||||||
if (err)
|
if (err)
|
||||||
return cb(err, null);
|
{
|
||||||
cb(undefined, ical.parseICS(data));
|
return cb(err, null);
|
||||||
|
}
|
||||||
|
else if (r.statusCode != 200)
|
||||||
|
{
|
||||||
|
return cb(r.statusCode + ": " + r.statusMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(undefined, ical.parseICS(data));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,40 +24,43 @@ exports.parseFile = function(filename){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var rrule = require('rrule-alt').RRule
|
var rrule = require('rrule').RRule
|
||||||
var rrulestr = rrule.rrulestr
|
|
||||||
|
|
||||||
ical.objectHandlers['RRULE'] = function(val, params, curr, stack, line){
|
ical.objectHandlers['RRULE'] = function(val, params, curr, stack, line){
|
||||||
curr.rrule = line;
|
curr.rrule = line;
|
||||||
return curr
|
return curr
|
||||||
}
|
}
|
||||||
var originalEnd = ical.objectHandlers['END'];
|
var originalEnd = ical.objectHandlers['END'];
|
||||||
ical.objectHandlers['END'] = function(val, params, curr, stack){
|
ical.objectHandlers['END'] = function (val, params, curr, stack) {
|
||||||
if (curr.rrule) {
|
// Recurrence rules are only valid for VEVENT, VTODO, and VJOURNAL.
|
||||||
var rule = curr.rrule;
|
// More specifically, we need to filter the VCALENDAR type because we might end up with a defined rrule
|
||||||
if (rule.indexOf('DTSTART') === -1) {
|
// due to the subtypes.
|
||||||
|
if ((val === "VEVENT") || (val === "VTODO") || (val === "VJOURNAL")) {
|
||||||
|
if (curr.rrule) {
|
||||||
|
var rule = curr.rrule.replace('RRULE:', '');
|
||||||
|
if (rule.indexOf('DTSTART') === -1) {
|
||||||
|
|
||||||
if (curr.start.length === 8) {
|
if (curr.start.length === 8) {
|
||||||
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(curr.start);
|
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(curr.start);
|
||||||
if (comps) {
|
if (comps) {
|
||||||
curr.start = new Date (comps[1], comps[2] - 1, comps[3]);
|
curr.start = new Date(comps[1], comps[2] - 1, comps[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rule += ' DTSTART:' + curr.start.toISOString().replace(/[-:]/g, '');
|
|
||||||
rule = rule.replace(/\.[0-9]{3}/, '');
|
if (typeof curr.start.toISOString === 'function') {
|
||||||
}
|
try {
|
||||||
for (var i in curr.exdates) {
|
rule += ';DTSTART=' + curr.start.toISOString().replace(/[-:]/g, '');
|
||||||
rule += ' EXDATE:' + curr.exdates[i].toISOString().replace(/[-:]/g, '');
|
rule = rule.replace(/\.[0-9]{3}/, '');
|
||||||
rule = rule.replace(/\.[0-9]{3}/, '');
|
} catch (error) {
|
||||||
}
|
console.error("ERROR when trying to convert to ISOString", error);
|
||||||
try {
|
}
|
||||||
curr.rrule = rrulestr(rule);
|
} else {
|
||||||
}
|
console.error("No toISOString function in curr.start", curr.start);
|
||||||
catch(err) {
|
}
|
||||||
console.log("Unrecognised element in calendar feed, ignoring: " + rule);
|
}
|
||||||
curr.rrule = null;
|
curr.rrule = rrule.fromString(rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return originalEnd.call(this, val, params, curr, stack);
|
return originalEnd.call(this, val, params, curr, stack);
|
||||||
}
|
}
|
||||||
|
@@ -10,17 +10,18 @@
|
|||||||
],
|
],
|
||||||
"homepage": "https://github.com/peterbraden/ical.js",
|
"homepage": "https://github.com/peterbraden/ical.js",
|
||||||
"author": "Peter Braden <peterbraden@peterbraden.co.uk> (peterbraden.co.uk)",
|
"author": "Peter Braden <peterbraden@peterbraden.co.uk> (peterbraden.co.uk)",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/peterbraden/ical.js.git"
|
"url": "git://github.com/peterbraden/ical.js.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"request": "2.68.0",
|
"request": "^2.88.0",
|
||||||
"rrule": "2.0.0"
|
"rrule": "2.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vows": "0.7.0",
|
"vows": "0.8.2",
|
||||||
"underscore": "1.3.0"
|
"underscore": "1.9.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/vows/bin/vows ./test/test.js"
|
"test": "./node_modules/vows/bin/vows ./test/test.js"
|
||||||
|
@@ -7,6 +7,7 @@ A tolerant, minimal icalendar parser for javascript/node
|
|||||||
(http://tools.ietf.org/html/rfc5545)
|
(http://tools.ietf.org/html/rfc5545)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Install - Node.js ##
|
## Install - Node.js ##
|
||||||
|
|
||||||
ical.js is availble on npm:
|
ical.js is availble on npm:
|
||||||
@@ -33,19 +34,29 @@ Use the request library to fetch the specified URL (```opts``` gets passed on to
|
|||||||
|
|
||||||
## Example 1 - Print list of upcoming node conferences (see example.js)
|
## Example 1 - Print list of upcoming node conferences (see example.js)
|
||||||
```javascript
|
```javascript
|
||||||
var ical = require('ical')
|
'use strict';
|
||||||
, months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
||||||
|
|
||||||
ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function(err, data) {
|
const ical = require('ical');
|
||||||
for (var k in data){
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
if (data.hasOwnProperty(k)) {
|
|
||||||
var ev = data[k]
|
ical.fromURL('http://lanyrd.com/topics/nodejs/nodejs.ics', {}, function (err, data) {
|
||||||
console.log("Conference",
|
for (let k in data) {
|
||||||
ev.summary,
|
if (data.hasOwnProperty(k)) {
|
||||||
'is in',
|
var ev = data[k];
|
||||||
ev.location,
|
if (data[k].type == 'VEVENT') {
|
||||||
'on the', ev.start.getDate(), 'of', months[ev.start.getMonth()]);
|
console.log(`${ev.summary} is in ${ev.location} on the ${ev.start.getDate()} of ${months[ev.start.getMonth()]} at ${ev.start.toLocaleTimeString('en-GB')}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Recurrences and Exceptions ##
|
||||||
|
Calendar events with recurrence rules can be significantly more complicated to handle correctly. There are three parts to handling them:
|
||||||
|
|
||||||
|
1. rrule - the recurrence rule specifying the pattern of recurring dates and times for the event.
|
||||||
|
2. recurrences - an optional array of event data that can override specific occurrences of the event.
|
||||||
|
3. exdate - an optional array of dates that should be excluded from the recurrence pattern.
|
||||||
|
|
||||||
|
See example_rrule.js for an example of handling recurring calendar events.
|
||||||
|
129
modules/default/calendar/vendor/ical.js/test/test.js
vendored
129
modules/default/calendar/vendor/ical.js/test/test.js
vendored
@@ -43,6 +43,12 @@ vows.describe('node-ical').addBatch({
|
|||||||
, 'has a summary (invalid colon handling tolerance)' : function(topic){
|
, 'has a summary (invalid colon handling tolerance)' : function(topic){
|
||||||
assert.equal(topic.summary, '[Async]: Everything Express')
|
assert.equal(topic.summary, '[Async]: Everything Express')
|
||||||
}
|
}
|
||||||
|
, 'has a date only start datetime' : function(topic){
|
||||||
|
assert.equal(topic.start.dateOnly, true)
|
||||||
|
}
|
||||||
|
, 'has a date only end datetime' : function(topic){
|
||||||
|
assert.equal(topic.end.dateOnly, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
, 'event d4c8' :{
|
, 'event d4c8' :{
|
||||||
topic : function(events){
|
topic : function(events){
|
||||||
@@ -108,7 +114,7 @@ vows.describe('node-ical').addBatch({
|
|||||||
assert.equal(topic.end.getFullYear(), 1998);
|
assert.equal(topic.end.getFullYear(), 1998);
|
||||||
assert.equal(topic.end.getUTCMonth(), 2);
|
assert.equal(topic.end.getUTCMonth(), 2);
|
||||||
assert.equal(topic.end.getUTCDate(), 15);
|
assert.equal(topic.end.getUTCDate(), 15);
|
||||||
assert.equal(topic.end.getUTCHours(), 0);
|
assert.equal(topic.end.getUTCHours(), 00);
|
||||||
assert.equal(topic.end.getUTCMinutes(), 30);
|
assert.equal(topic.end.getUTCMinutes(), 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +152,7 @@ vows.describe('node-ical').addBatch({
|
|||||||
}
|
}
|
||||||
, 'has a start datetime' : function(topic) {
|
, 'has a start datetime' : function(topic) {
|
||||||
assert.equal(topic.start.getFullYear(), 2011);
|
assert.equal(topic.start.getFullYear(), 2011);
|
||||||
assert.equal(topic.start.getMonth(), 9);
|
assert.equal(topic.start.getMonth(), 09);
|
||||||
assert.equal(topic.start.getDate(), 11);
|
assert.equal(topic.start.getDate(), 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +198,12 @@ vows.describe('node-ical').addBatch({
|
|||||||
}
|
}
|
||||||
, 'has a start' : function(topic){
|
, 'has a start' : function(topic){
|
||||||
assert.equal(topic.start.tz, 'America/Phoenix')
|
assert.equal(topic.start.tz, 'America/Phoenix')
|
||||||
assert.equal(topic.start.toISOString(), new Date(2011, 10, 9, 19, 0,0).toISOString())
|
assert.equal(topic.start.toISOString(), new Date(2011, 10, 09, 19, 0,0).toISOString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
, 'with test6.ics (testing assembly.org)' : {
|
, 'with test6.ics (testing assembly.org)': {
|
||||||
topic: function () {
|
topic: function () {
|
||||||
return ical.parseFile('./test/test6.ics')
|
return ical.parseFile('./test/test6.ics')
|
||||||
}
|
}
|
||||||
@@ -208,13 +214,13 @@ vows.describe('node-ical').addBatch({
|
|||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
, 'has a start' : function(topic){
|
, 'has a start' : function(topic){
|
||||||
assert.equal(topic.start.toISOString(), new Date(2011, 7, 4, 12, 0,0).toISOString())
|
assert.equal(topic.start.toISOString(), new Date(2011, 07, 04, 12, 0,0).toISOString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
, 'event with rrule' :{
|
, 'event with rrule' :{
|
||||||
topic: function(events){
|
topic: function(events){
|
||||||
return _.select(_.values(events), function(x){
|
return _.select(_.values(events), function(x){
|
||||||
return x.summary == "foobarTV broadcast starts"
|
return x.summary === "foobarTV broadcast starts"
|
||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
, "Has an RRULE": function(topic){
|
, "Has an RRULE": function(topic){
|
||||||
@@ -249,7 +255,7 @@ vows.describe('node-ical').addBatch({
|
|||||||
},
|
},
|
||||||
'task completed': function(task){
|
'task completed': function(task){
|
||||||
assert.equal(task.completion, 100);
|
assert.equal(task.completion, 100);
|
||||||
assert.equal(task.completed.toISOString(), new Date(2013, 6, 16, 10, 57, 45).toISOString());
|
assert.equal(task.completed.toISOString(), new Date(2013, 06, 16, 10, 57, 45).toISOString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,14 +373,115 @@ vows.describe('node-ical').addBatch({
|
|||||||
assert.equal(topic.end.getFullYear(), 2014);
|
assert.equal(topic.end.getFullYear(), 2014);
|
||||||
assert.equal(topic.end.getMonth(), 3);
|
assert.equal(topic.end.getMonth(), 3);
|
||||||
assert.equal(topic.end.getUTCHours(), 19);
|
assert.equal(topic.end.getUTCHours(), 19);
|
||||||
assert.equal(topic.end.getUTCMinutes(), 0);
|
assert.equal(topic.end.getUTCMinutes(), 00);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
'url request errors' : {
|
, 'with test12.ics (testing recurrences and exdates)': {
|
||||||
|
topic: function () {
|
||||||
|
return ical.parseFile('./test/test12.ics')
|
||||||
|
}
|
||||||
|
, 'event with rrule': {
|
||||||
|
topic: function (events) {
|
||||||
|
return _.select(_.values(events), function (x) {
|
||||||
|
return x.uid === '0000001';
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
, "Has an RRULE": function (topic) {
|
||||||
|
assert.notEqual(topic.rrule, undefined);
|
||||||
|
}
|
||||||
|
, "Has summary Treasure Hunting": function (topic) {
|
||||||
|
assert.equal(topic.summary, 'Treasure Hunting');
|
||||||
|
}
|
||||||
|
, "Has two EXDATES": function (topic) {
|
||||||
|
assert.notEqual(topic.exdate, undefined);
|
||||||
|
assert.notEqual(topic.exdate[new Date(2015, 06, 08, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
assert.notEqual(topic.exdate[new Date(2015, 06, 10, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
}
|
||||||
|
, "Has a RECURRENCE-ID override": function (topic) {
|
||||||
|
assert.notEqual(topic.recurrences, undefined);
|
||||||
|
assert.notEqual(topic.recurrences[new Date(2015, 06, 07, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
assert.equal(topic.recurrences[new Date(2015, 06, 07, 12, 0, 0).toISOString().substring(0, 10)].summary, 'More Treasure Hunting');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, 'with test13.ics (testing recurrence-id before rrule)': {
|
||||||
|
topic: function () {
|
||||||
|
return ical.parseFile('./test/test13.ics')
|
||||||
|
}
|
||||||
|
, 'event with rrule': {
|
||||||
|
topic: function (events) {
|
||||||
|
return _.select(_.values(events), function (x) {
|
||||||
|
return x.uid === '6m2q7kb2l02798oagemrcgm6pk@google.com';
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
, "Has an RRULE": function (topic) {
|
||||||
|
assert.notEqual(topic.rrule, undefined);
|
||||||
|
}
|
||||||
|
, "Has summary 'repeated'": function (topic) {
|
||||||
|
assert.equal(topic.summary, 'repeated');
|
||||||
|
}
|
||||||
|
, "Has a RECURRENCE-ID override": function (topic) {
|
||||||
|
assert.notEqual(topic.recurrences, undefined);
|
||||||
|
assert.notEqual(topic.recurrences[new Date(2016, 7, 26, 14, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
assert.equal(topic.recurrences[new Date(2016, 7, 26, 14, 0, 0).toISOString().substring(0, 10)].summary, 'bla bla');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, 'with test14.ics (testing comma-separated exdates)': {
|
||||||
|
topic: function () {
|
||||||
|
return ical.parseFile('./test/test14.ics')
|
||||||
|
}
|
||||||
|
, 'event with comma-separated exdate': {
|
||||||
|
topic: function (events) {
|
||||||
|
return _.select(_.values(events), function (x) {
|
||||||
|
return x.uid === '98765432-ABCD-DCBB-999A-987765432123';
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
, "Has summary 'Example of comma-separated exdates'": function (topic) {
|
||||||
|
assert.equal(topic.summary, 'Example of comma-separated exdates');
|
||||||
|
}
|
||||||
|
, "Has four comma-separated EXDATES": function (topic) {
|
||||||
|
assert.notEqual(topic.exdate, undefined);
|
||||||
|
// Verify the four comma-separated EXDATES are there
|
||||||
|
assert.notEqual(topic.exdate[new Date(2017, 6, 6, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
assert.notEqual(topic.exdate[new Date(2017, 6, 17, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
assert.notEqual(topic.exdate[new Date(2017, 6, 20, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
assert.notEqual(topic.exdate[new Date(2017, 7, 3, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
// Verify an arbitrary date isn't there
|
||||||
|
assert.equal(topic.exdate[new Date(2017, 4, 5, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, 'with test14.ics (testing exdates with bad times)': {
|
||||||
|
topic: function () {
|
||||||
|
return ical.parseFile('./test/test14.ics')
|
||||||
|
}
|
||||||
|
, 'event with exdates with bad times': {
|
||||||
|
topic: function (events) {
|
||||||
|
return _.select(_.values(events), function (x) {
|
||||||
|
return x.uid === '1234567-ABCD-ABCD-ABCD-123456789012';
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
, "Has summary 'Example of exdate with bad times'": function (topic) {
|
||||||
|
assert.equal(topic.summary, 'Example of exdate with bad times');
|
||||||
|
}
|
||||||
|
, "Has two EXDATES even though they have bad times": function (topic) {
|
||||||
|
assert.notEqual(topic.exdate, undefined);
|
||||||
|
// Verify the two EXDATES are there, even though they have bad times
|
||||||
|
assert.notEqual(topic.exdate[new Date(2017, 11, 18, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
assert.notEqual(topic.exdate[new Date(2017, 11, 19, 12, 0, 0).toISOString().substring(0, 10)], undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, 'url request errors': {
|
||||||
topic : function () {
|
topic : function () {
|
||||||
ical.fromURL('http://not.exist/', {}, this.callback);
|
ical.fromURL('http://255.255.255.255/', {}, this.callback);
|
||||||
}
|
}
|
||||||
, 'are passed back to the callback' : function (err, result) {
|
, 'are passed back to the callback' : function (err, result) {
|
||||||
assert.instanceOf(err, Error);
|
assert.instanceOf(err, Error);
|
||||||
|
19
modules/default/calendar/vendor/ical.js/test/test12.ics
vendored
Normal file
19
modules/default/calendar/vendor/ical.js/test/test12.ics
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:0000001
|
||||||
|
SUMMARY:Treasure Hunting
|
||||||
|
DTSTART;TZID=America/Los_Angeles:20150706T120000
|
||||||
|
DTEND;TZID=America/Los_Angeles:20150706T130000
|
||||||
|
RRULE:FREQ=DAILY;COUNT=10
|
||||||
|
EXDATE;TZID=America/Los_Angeles:20150708T120000
|
||||||
|
EXDATE;TZID=America/Los_Angeles:20150710T120000
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:0000001
|
||||||
|
SUMMARY:More Treasure Hunting
|
||||||
|
LOCATION:The other island
|
||||||
|
DTSTART;TZID=America/Los_Angeles:20150709T150000
|
||||||
|
DTEND;TZID=America/Los_Angeles:20150707T160000
|
||||||
|
RECURRENCE-ID;TZID=America/Los_Angeles:20150707T120000
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
57
modules/default/calendar/vendor/ical.js/test/test13.ics
vendored
Normal file
57
modules/default/calendar/vendor/ical.js/test/test13.ics
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Google Inc//Google Calendar 70.9054//EN
|
||||||
|
VERSION:2.0
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-WR-CALNAME:ical
|
||||||
|
X-WR-TIMEZONE:Europe/Kiev
|
||||||
|
X-WR-CALDESC:
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Kiev
|
||||||
|
X-LIC-LOCATION:Europe/Kiev
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0300
|
||||||
|
TZNAME:EEST
|
||||||
|
DTSTART:19700329T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0300
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
TZNAME:EET
|
||||||
|
DTSTART:19701025T040000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Europe/Kiev:20160826T140000
|
||||||
|
DTEND;TZID=Europe/Kiev:20160826T150000
|
||||||
|
DTSTAMP:20160825T061505Z
|
||||||
|
UID:6m2q7kb2l02798oagemrcgm6pk@google.com
|
||||||
|
RECURRENCE-ID;TZID=Europe/Kiev:20160826T140000
|
||||||
|
CREATED:20160823T125221Z
|
||||||
|
DESCRIPTION:
|
||||||
|
LAST-MODIFIED:20160823T130320Z
|
||||||
|
LOCATION:
|
||||||
|
SEQUENCE:0
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:bla bla
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Europe/Kiev:20160825T140000
|
||||||
|
DTEND;TZID=Europe/Kiev:20160825T150000
|
||||||
|
RRULE:FREQ=DAILY;UNTIL=20160828T110000Z
|
||||||
|
DTSTAMP:20160825T061505Z
|
||||||
|
UID:6m2q7kb2l02798oagemrcgm6pk@google.com
|
||||||
|
CREATED:20160823T125221Z
|
||||||
|
DESCRIPTION:
|
||||||
|
LAST-MODIFIED:20160823T125221Z
|
||||||
|
LOCATION:
|
||||||
|
SEQUENCE:0
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:repeated
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
33
modules/default/calendar/vendor/ical.js/test/test14.ics
vendored
Normal file
33
modules/default/calendar/vendor/ical.js/test/test14.ics
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Google Inc//Google Calendar 70.9054//EN
|
||||||
|
VERSION:2.0
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-WR-CALNAME:ical
|
||||||
|
X-WR-TIMEZONE:Europe/Kiev
|
||||||
|
X-WR-CALDESC:
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:98765432-ABCD-DCBB-999A-987765432123
|
||||||
|
DTSTART;TZID=US/Central:20170216T090000
|
||||||
|
DTEND;TZID=US/Central:20170216T190000
|
||||||
|
DTSTAMP:20170727T044436Z
|
||||||
|
EXDATE;TZID=US/Central:20170706T090000,20170717T090000,20170720T090000,20
|
||||||
|
170803T090000
|
||||||
|
LAST-MODIFIED:20170727T044435Z
|
||||||
|
RRULE:FREQ=WEEKLY;WKST=SU;UNTIL=20170814T045959Z;INTERVAL=2;BYDAY=MO,TH
|
||||||
|
SEQUENCE:0
|
||||||
|
SUMMARY:Example of comma-separated exdates
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
UID:1234567-ABCD-ABCD-ABCD-123456789012
|
||||||
|
DTSTART:20170814T140000Z
|
||||||
|
DTEND:20170815T000000Z
|
||||||
|
DTSTAMP:20171204T134925Z
|
||||||
|
EXDATE:20171219T060000
|
||||||
|
EXDATE:20171218T060000
|
||||||
|
LAST-MODIFIED:20171024T140004Z
|
||||||
|
RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=2;BYDAY=MO,TU
|
||||||
|
SEQUENCE:0
|
||||||
|
SUMMARY:Example of exdate with bad times
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
@@ -44,3 +44,14 @@ The following properties can be configured:
|
|||||||
| `analogPlacement` | **Specific to the analog clock. _(requires displayType set to `'both'`)_** Specifies where the analog clock is in relation to the digital clock <br><br> **Possible values:** `top`, `right`, `bottom`, or `left` <br> **Default value:** `bottom`
|
| `analogPlacement` | **Specific to the analog clock. _(requires displayType set to `'both'`)_** Specifies where the analog clock is in relation to the digital clock <br><br> **Possible values:** `top`, `right`, `bottom`, or `left` <br> **Default value:** `bottom`
|
||||||
| `analogShowDate` | **Specific to the analog clock.** If the clock is used as a separate module and set to analog only, this configures whether a date is also displayed with the clock. <br><br> **Possible values:** `false`, `top`, or `bottom` <br> **Default value:** `top`
|
| `analogShowDate` | **Specific to the analog clock.** If the clock is used as a separate module and set to analog only, this configures whether a date is also displayed with the clock. <br><br> **Possible values:** `false`, `top`, or `bottom` <br> **Default value:** `top`
|
||||||
| `timezone` | Specific a timezone to show clock. <br><br> **Possible examples values:** `America/New_York`, `America/Santiago`, `Etc/GMT+10` <br> **Default value:** `none`. See more informations about configuration value [here](https://momentjs.com/timezone/docs/#/data-formats/packed-format/)
|
| `timezone` | Specific a timezone to show clock. <br><br> **Possible examples values:** `America/New_York`, `America/Santiago`, `Etc/GMT+10` <br> **Default value:** `none`. See more informations about configuration value [here](https://momentjs.com/timezone/docs/#/data-formats/packed-format/)
|
||||||
|
|
||||||
|
## Notifications
|
||||||
|
|
||||||
|
The clock makes use of the built-in [Notification Mechanism](https://github.com/michMich/MagicMirror/wiki/notifications) to relay notifications to all modules.
|
||||||
|
|
||||||
|
Current notifications are:
|
||||||
|
|
||||||
|
| Notification | Description
|
||||||
|
| ----------------- | -----------
|
||||||
|
| `CLOCK_SECOND` | A second has elapsed. <br> *Parameter*: second value
|
||||||
|
| `CLOCK_MINUTE` | A minute has elapsed <br> *Parameter*: minute value
|
||||||
|
@@ -41,11 +41,25 @@ Module.register("clock",{
|
|||||||
|
|
||||||
// Schedule update interval.
|
// Schedule update interval.
|
||||||
var self = this;
|
var self = this;
|
||||||
|
self.second = 0;
|
||||||
|
self.minute = 0;
|
||||||
self.lastDisplayedMinute = null;
|
self.lastDisplayedMinute = null;
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
if (self.config.displaySeconds || self.lastDisplayedMinute !== moment().minute()) {
|
if (self.config.displaySeconds || self.lastDisplayedMinute !== moment().minute()) {
|
||||||
self.updateDom();
|
self.updateDom();
|
||||||
}
|
}
|
||||||
|
if (self.second === 59) {
|
||||||
|
self.second = 0;
|
||||||
|
if (self.minute === 59){
|
||||||
|
self.minute = 0;
|
||||||
|
} else {
|
||||||
|
self.minute++;
|
||||||
|
}
|
||||||
|
self.sendNotification("CLOCK_MINUTE", self.minute);
|
||||||
|
} else {
|
||||||
|
self.second++;
|
||||||
|
self.sendNotification("CLOCK_SECOND", self.second);
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// Set locale.
|
// Set locale.
|
||||||
@@ -65,12 +79,12 @@ Module.register("clock",{
|
|||||||
var timeWrapper = document.createElement("div");
|
var timeWrapper = document.createElement("div");
|
||||||
var secondsWrapper = document.createElement("sup");
|
var secondsWrapper = document.createElement("sup");
|
||||||
var periodWrapper = document.createElement("span");
|
var periodWrapper = document.createElement("span");
|
||||||
var weekWrapper = document.createElement("div")
|
var weekWrapper = document.createElement("div");
|
||||||
// Style Wrappers
|
// Style Wrappers
|
||||||
dateWrapper.className = "date normal medium";
|
dateWrapper.className = "date normal medium";
|
||||||
timeWrapper.className = "time bright large light";
|
timeWrapper.className = "time bright large light";
|
||||||
secondsWrapper.className = "dimmed";
|
secondsWrapper.className = "dimmed";
|
||||||
weekWrapper.className = "week dimmed medium"
|
weekWrapper.className = "week dimmed medium";
|
||||||
|
|
||||||
// Set content of wrappers.
|
// Set content of wrappers.
|
||||||
// The moment().format("h") method has a bug on the Raspberry Pi.
|
// The moment().format("h") method has a bug on the Raspberry Pi.
|
||||||
@@ -136,7 +150,7 @@ Module.register("clock",{
|
|||||||
clockCircle.style.width = this.config.analogSize;
|
clockCircle.style.width = this.config.analogSize;
|
||||||
clockCircle.style.height = this.config.analogSize;
|
clockCircle.style.height = this.config.analogSize;
|
||||||
|
|
||||||
if (this.config.analogFace != "" && this.config.analogFace != "simple" && this.config.analogFace != "none") {
|
if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") {
|
||||||
clockCircle.style.background = "url("+ this.data.path + "faces/" + this.config.analogFace + ".svg)";
|
clockCircle.style.background = "url("+ this.data.path + "faces/" + this.config.analogFace + ".svg)";
|
||||||
clockCircle.style.backgroundSize = "100%";
|
clockCircle.style.backgroundSize = "100%";
|
||||||
|
|
||||||
@@ -144,7 +158,7 @@ Module.register("clock",{
|
|||||||
// clockCircle.style.border = "1px solid black";
|
// clockCircle.style.border = "1px solid black";
|
||||||
clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
|
clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
|
||||||
|
|
||||||
} else if (this.config.analogFace != "none") {
|
} else if (this.config.analogFace !== "none") {
|
||||||
clockCircle.style.border = "2px solid white";
|
clockCircle.style.border = "2px solid white";
|
||||||
}
|
}
|
||||||
var clockFace = document.createElement("div");
|
var clockFace = document.createElement("div");
|
||||||
|
@@ -54,7 +54,7 @@ Module.register("compliments", {
|
|||||||
this.lastComplimentIndex = -1;
|
this.lastComplimentIndex = -1;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
if (this.config.remoteFile != null) {
|
if (this.config.remoteFile !== null) {
|
||||||
this.complimentFile(function(response) {
|
this.complimentFile(function(response) {
|
||||||
self.config.compliments = JSON.parse(response);
|
self.config.compliments = JSON.parse(response);
|
||||||
self.updateDom();
|
self.updateDom();
|
||||||
@@ -134,7 +134,7 @@ Module.register("compliments", {
|
|||||||
xobj.overrideMimeType("application/json");
|
xobj.overrideMimeType("application/json");
|
||||||
xobj.open("GET", path, true);
|
xobj.open("GET", path, true);
|
||||||
xobj.onreadystatechange = function() {
|
xobj.onreadystatechange = function() {
|
||||||
if (xobj.readyState == 4 && xobj.status == "200") {
|
if (xobj.readyState === 4 && xobj.status === 200) {
|
||||||
callback(xobj.responseText);
|
callback(xobj.responseText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -165,7 +165,6 @@ Module.register("compliments", {
|
|||||||
return wrapper;
|
return wrapper;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// From data currentweather set weather type
|
// From data currentweather set weather type
|
||||||
setCurrentWeatherType: function(data) {
|
setCurrentWeatherType: function(data) {
|
||||||
var weatherIconTable = {
|
var weatherIconTable = {
|
||||||
@@ -191,10 +190,9 @@ Module.register("compliments", {
|
|||||||
this.currentWeatherType = weatherIconTable[data.weather[0].icon];
|
this.currentWeatherType = weatherIconTable[data.weather[0].icon];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Override notification handler.
|
// Override notification handler.
|
||||||
notificationReceived: function(notification, payload, sender) {
|
notificationReceived: function(notification, payload, sender) {
|
||||||
if (notification == "CURRENTWEATHER_DATA") {
|
if (notification === "CURRENTWEATHER_DATA") {
|
||||||
this.setCurrentWeatherType(payload.data);
|
this.setCurrentWeatherType(payload.data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -353,7 +353,7 @@ Module.register("currentweather",{
|
|||||||
} 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) {
|
||||||
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon
|
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
|
||||||
} else if (this.firstEvent && this.firstEvent.location) {
|
} else if (this.firstEvent && this.firstEvent.location) {
|
||||||
params += "q=" + this.firstEvent.location;
|
params += "q=" + this.firstEvent.location;
|
||||||
} else {
|
} else {
|
||||||
@@ -383,6 +383,7 @@ Module.register("currentweather",{
|
|||||||
|
|
||||||
this.humidity = parseFloat(data.main.humidity);
|
this.humidity = parseFloat(data.main.humidity);
|
||||||
this.temperature = this.roundValue(data.main.temp);
|
this.temperature = this.roundValue(data.main.temp);
|
||||||
|
this.fetchedLocationName = data.name;
|
||||||
this.feelsLike = 0;
|
this.feelsLike = 0;
|
||||||
|
|
||||||
if (this.config.useBeaufort){
|
if (this.config.useBeaufort){
|
||||||
|
@@ -15,10 +15,10 @@ Module.register("helloworld",{
|
|||||||
},
|
},
|
||||||
|
|
||||||
getTemplate: function () {
|
getTemplate: function () {
|
||||||
return "helloworld.njk"
|
return "helloworld.njk";
|
||||||
},
|
},
|
||||||
|
|
||||||
getTemplateData: function () {
|
getTemplateData: function () {
|
||||||
return this.config
|
return this.config;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -45,9 +45,17 @@ MagicMirror's [notification mechanism](https://github.com/MichMich/MagicMirror/t
|
|||||||
| `ARTICLE_PREVIOUS` | Shows the previous news title (hiding the summary or previously fully displayed article)
|
| `ARTICLE_PREVIOUS` | Shows the previous news title (hiding the summary or previously fully displayed article)
|
||||||
| `ARTICLE_MORE_DETAILS` | When received the _first time_, shows the corresponding description of the currently displayed news title. <br> The module expects that the module's configuration option `showDescription` is set to `false` (default value). <br><br> When received a _second consecutive time_, shows the full news article in an IFRAME. <br> This requires that the news page can be embedded in an IFRAME, e.g. doesn't have the HTTP response header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) set to e.g. `DENY`.<br><br>When received the _next consecutive times_, reloads the page and scrolls down by `scrollLength` pixels to paginate through the article.
|
| `ARTICLE_MORE_DETAILS` | When received the _first time_, shows the corresponding description of the currently displayed news title. <br> The module expects that the module's configuration option `showDescription` is set to `false` (default value). <br><br> When received a _second consecutive time_, shows the full news article in an IFRAME. <br> This requires that the news page can be embedded in an IFRAME, e.g. doesn't have the HTTP response header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) set to e.g. `DENY`.<br><br>When received the _next consecutive times_, reloads the page and scrolls down by `scrollLength` pixels to paginate through the article.
|
||||||
| `ARTICLE_LESS_DETAILS` | Hides the summary or full news article and only displays the news title of the currently viewed news item.
|
| `ARTICLE_LESS_DETAILS` | Hides the summary or full news article and only displays the news title of the currently viewed news item.
|
||||||
| `ARTICLE_TOGGLE_FULL` | Toogles article in fullscreen.
|
| `ARTICLE_TOGGLE_FULL` | Toggles article in fullscreen.
|
||||||
| `ARTICLE_INFO_REQUEST` | Causes `newsfeed` to respond with the notification `ARTICLE_INFO_RESPONSE`, the payload of which provides the `title`, `source`, `date`, `desc` and `url` of the current news title.
|
| `ARTICLE_INFO_REQUEST` | Causes `newsfeed` to respond with the notification `ARTICLE_INFO_RESPONSE`, the payload of which provides the `title`, `source`, `date`, `desc` and `url` of the current news title.
|
||||||
|
|
||||||
|
#### Notifications sent by the module
|
||||||
|
MagicMirror's [notification mechanism](https://github.com/MichMich/MagicMirror/tree/master/modules#thissendnotificationnotification-payload) can also be used to send notifications from the current module to all other modules. The following notifications are broadcasted from this module:
|
||||||
|
|
||||||
|
| Notification Identifier | Description
|
||||||
|
| ----------------------- | -----------
|
||||||
|
| `NEWS_FEED` | Broadcast the current list of news items.
|
||||||
|
| `NEWS_FEED_UPDATE` | Broadcasts the list of updates news items.
|
||||||
|
|
||||||
Note the payload of the sent notification event is ignored.
|
Note the payload of the sent notification event is ignored.
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
@@ -68,6 +76,8 @@ The following properties can be configured:
|
|||||||
| `feeds` | An array of feed urls that will be used as source. <br> More info about this object can be found below. <br> **Default value:** `[{ title: "New York Times", url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", encoding: "UTF-8" }]`<br>You can add `reloadInterval` option to set particular reloadInterval to a feed.
|
| `feeds` | An array of feed urls that will be used as source. <br> More info about this object can be found below. <br> **Default value:** `[{ title: "New York Times", url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", encoding: "UTF-8" }]`<br>You can add `reloadInterval` option to set particular reloadInterval to a feed.
|
||||||
| `showSourceTitle` | Display the title of the source. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
| `showSourceTitle` | Display the title of the source. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
||||||
| `showPublishDate` | Display the publish date of an headline. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
| `showPublishDate` | Display the publish date of an headline. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
||||||
|
| `broadcastNewsFeeds` | Gives the ability to broadcast news feeds to all modules, by using ```sendNotification()``` when set to `true`, rather than ```sendSocketNotification()``` when `false` <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
||||||
|
| `broadcastNewsUpdates` | Gives the ability to broadcast news feed updates to all modules <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
||||||
| `showDescription` | Display the description of an item. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
|
| `showDescription` | Display the description of an item. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
|
||||||
| `wrapTitle` | Wrap the title of the item to multiple lines. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
| `wrapTitle` | Wrap the title of the item to multiple lines. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
||||||
| `wrapDescription` | Wrap the description of the item to multiple lines. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
| `wrapDescription` | Wrap the description of the item to multiple lines. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
||||||
@@ -80,7 +90,7 @@ The following properties can be configured:
|
|||||||
| `maxNewsItems` | Total amount of news items to cycle through. (0 for unlimited) <br><br> **Possible values:**`0` - `...` <br> **Default value:** `0`
|
| `maxNewsItems` | Total amount of news items to cycle through. (0 for unlimited) <br><br> **Possible values:**`0` - `...` <br> **Default value:** `0`
|
||||||
| `ignoreOldItems` | Ignore news items that are outdated. <br><br> **Possible values:**`true` or `false` <br> **Default value:** `false`
|
| `ignoreOldItems` | Ignore news items that are outdated. <br><br> **Possible values:**`true` or `false` <br> **Default value:** `false`
|
||||||
| `ignoreOlderThan` | How old should news items be before they are considered outdated? (Milliseconds) <br><br> **Possible values:**`1` - `...` <br> **Default value:** `86400000` (1 day)
|
| `ignoreOlderThan` | How old should news items be before they are considered outdated? (Milliseconds) <br><br> **Possible values:**`1` - `...` <br> **Default value:** `86400000` (1 day)
|
||||||
| `removeStartTags` | Some newsfeeds feature tags at the **beginning** of their titles or descriptions, such as _[VIDEO]_. This setting allows for the removal of specified tags from the beginning of an item's description and/or title. <br><br> **Possible values:**`'title'`, `'description'`, `'both'`
|
| `removeStartTags` | Some news feeds feature tags at the **beginning** of their titles or descriptions, such as _[VIDEO]_. This setting allows for the removal of specified tags from the beginning of an item's description and/or title. <br><br> **Possible values:**`'title'`, `'description'`, `'both'`
|
||||||
| `startTags` | List the tags you would like to have removed at the beginning of the feed item <br><br> **Possible values:** `['TAG']` or `['TAG1','TAG2',...]`
|
| `startTags` | List the tags you would like to have removed at the beginning of the feed item <br><br> **Possible values:** `['TAG']` or `['TAG1','TAG2',...]`
|
||||||
| `removeEndTags` | Remove specified tags from the **end** of an item's description and/or title. <br><br> **Possible values:**`'title'`, `'description'`, `'both'`
|
| `removeEndTags` | Remove specified tags from the **end** of an item's description and/or title. <br><br> **Possible values:**`'title'`, `'description'`, `'both'`
|
||||||
| `endTags` | List the tags you would like to have removed at the end of the feed item <br><br> **Possible values:** `['TAG']` or `['TAG1','TAG2',...]`
|
| `endTags` | List the tags you would like to have removed at the end of the feed item <br><br> **Possible values:** `['TAG']` or `['TAG1','TAG2',...]`
|
||||||
|
@@ -81,11 +81,10 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
|
|||||||
scheduleTimer();
|
scheduleTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||||
headers = {"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)",
|
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",
|
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
|
||||||
"Pragma": "no-cache"}
|
"Pragma": "no-cache"};
|
||||||
|
|
||||||
request({uri: url, encoding: null, headers: headers})
|
request({uri: url, encoding: null, headers: headers})
|
||||||
.on("error", function(error) {
|
.on("error", function(error) {
|
||||||
|
@@ -20,6 +20,8 @@ Module.register("newsfeed",{
|
|||||||
],
|
],
|
||||||
showSourceTitle: true,
|
showSourceTitle: true,
|
||||||
showPublishDate: true,
|
showPublishDate: true,
|
||||||
|
broadcastNewsFeeds: true,
|
||||||
|
broadcastNewsUpdates: true,
|
||||||
showDescription: false,
|
showDescription: false,
|
||||||
wrapTitle: true,
|
wrapTitle: true,
|
||||||
wrapDescription: true,
|
wrapDescription: true,
|
||||||
@@ -189,7 +191,7 @@ Module.register("newsfeed",{
|
|||||||
fullArticle.style.top = "0";
|
fullArticle.style.top = "0";
|
||||||
fullArticle.style.left = "0";
|
fullArticle.style.left = "0";
|
||||||
fullArticle.style.border = "none";
|
fullArticle.style.border = "none";
|
||||||
fullArticle.src = this.getActiveItemURL()
|
fullArticle.src = this.getActiveItemURL();
|
||||||
fullArticle.style.zIndex = 1;
|
fullArticle.style.zIndex = 1;
|
||||||
wrapper.appendChild(fullArticle);
|
wrapper.appendChild(fullArticle);
|
||||||
}
|
}
|
||||||
@@ -266,6 +268,20 @@ Module.register("newsfeed",{
|
|||||||
}, this);
|
}, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get updated news items and broadcast them
|
||||||
|
var updatedItems = [];
|
||||||
|
newsItems.forEach(value => {
|
||||||
|
if (this.newsItems.findIndex(value1 => value1 === value) === -1) {
|
||||||
|
// Add item to updated items list
|
||||||
|
updatedItems.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if updated items exist, if so and if we should broadcast these updates, then lets do so
|
||||||
|
if (this.config.broadcastNewsUpdates && updatedItems.length > 0) {
|
||||||
|
this.sendNotification("NEWS_FEED_UPDATE", {items: updatedItems});
|
||||||
|
}
|
||||||
|
|
||||||
this.newsItems = newsItems;
|
this.newsItems = newsItems;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -314,6 +330,11 @@ Module.register("newsfeed",{
|
|||||||
timer = setInterval(function() {
|
timer = setInterval(function() {
|
||||||
self.activeItem++;
|
self.activeItem++;
|
||||||
self.updateDom(self.config.animationSpeed);
|
self.updateDom(self.config.animationSpeed);
|
||||||
|
|
||||||
|
// Broadcast NewsFeed if needed
|
||||||
|
if (self.config.broadcastNewsFeeds) {
|
||||||
|
self.sendNotification("NEWS_FEED", {items: self.newsItems});
|
||||||
|
}
|
||||||
}, this.config.updateInterval);
|
}, this.config.updateInterval);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -398,7 +419,7 @@ Module.register("newsfeed",{
|
|||||||
date: this.newsItems[this.activeItem].pubdate,
|
date: this.newsItems[this.activeItem].pubdate,
|
||||||
desc: this.newsItems[this.activeItem].description,
|
desc: this.newsItems[this.activeItem].description,
|
||||||
url: this.getActiveItemURL()
|
url: this.getActiveItemURL()
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
Log.info(this.name + " - unknown notification, ignoring: " + notification);
|
Log.info(this.name + " - unknown notification, ignoring: " + notification);
|
||||||
}
|
}
|
||||||
|
@@ -79,7 +79,7 @@ module.exports = NodeHelper.create({
|
|||||||
|
|
||||||
scheduleNextFetch: function(delay) {
|
scheduleNextFetch: function(delay) {
|
||||||
if (delay < 60 * 1000) {
|
if (delay < 60 * 1000) {
|
||||||
delay = 60 * 1000
|
delay = 60 * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@@ -8,7 +8,6 @@ Module.register("updatenotification", {
|
|||||||
|
|
||||||
start: function () {
|
start: function () {
|
||||||
Log.log("Start updatenotification");
|
Log.log("Start updatenotification");
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
notificationReceived: function (notification, payload, sender) {
|
notificationReceived: function (notification, payload, sender) {
|
||||||
@@ -58,14 +57,14 @@ Module.register("updatenotification", {
|
|||||||
icon.innerHTML = " ";
|
icon.innerHTML = " ";
|
||||||
message.appendChild(icon);
|
message.appendChild(icon);
|
||||||
|
|
||||||
var updateInfoKeyName = this.status.behind == 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
|
var updateInfoKeyName = this.status.behind === 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
|
||||||
var subtextHtml = this.translate(updateInfoKeyName, {
|
var subtextHtml = this.translate(updateInfoKeyName, {
|
||||||
COMMIT_COUNT: this.status.behind,
|
COMMIT_COUNT: this.status.behind,
|
||||||
BRANCH_NAME: this.status.current
|
BRANCH_NAME: this.status.current
|
||||||
});
|
});
|
||||||
|
|
||||||
var text = document.createElement("span");
|
var text = document.createElement("span");
|
||||||
if (this.status.module == "default") {
|
if (this.status.module === "default") {
|
||||||
text.innerHTML = this.translate("UPDATE_NOTIFICATION");
|
text.innerHTML = this.translate("UPDATE_NOTIFICATION");
|
||||||
subtextHtml = this.diffLink(subtextHtml);
|
subtextHtml = this.diffLink(subtextHtml);
|
||||||
} else {
|
} else {
|
||||||
|
12
modules/default/weather/README.md
Normal file → Executable file
12
modules/default/weather/README.md
Normal file → Executable file
@@ -35,9 +35,11 @@ The following properties can be configured:
|
|||||||
|
|
||||||
| Option | Description
|
| Option | Description
|
||||||
| ---------------------------- | -----------
|
| ---------------------------- | -----------
|
||||||
| `weatherProvider` | Which weather provider should be used. <br><br> **Possible values:** `openweathermap` , `darksky` , or `weathergov` <br> **Default value:** `openweathermap`
|
| `weatherProvider` | Which weather provider should be used. <br><br> **Possible values:** `openweathermap` , `darksky` , `weathergov` or `ukmetoffice`<br> **Default value:** `openweathermap`
|
||||||
| `type` | Which type of weather data should be displayed. <br><br> **Possible values:** `current` or `forecast` <br> **Default value:** `current`
|
| `type` | Which type of weather data should be displayed. <br><br> **Possible values:** `current` or `forecast` <br> **Default value:** `current`
|
||||||
| `units` | What units to use. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` = Fahrenheit <br> **Default value:** `config.units`
|
| `units` | What units to use. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` = Fahrenheit <br> **Default value:** `config.units`
|
||||||
|
| `tempUnits` | What units to use for temperature. If specified overrides `units` setting. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` = Fahrenheit <br> **Default value:** `units`
|
||||||
|
| `windUnits` | What units to use for wind speed. If specified overrides `units` setting. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` = Fahrenheit <br> **Default value:** `units`
|
||||||
| `roundTemp` | Round temperature value to nearest integer. <br><br> **Possible values:** `true` (round to integer) or `false` (display exact value with decimal point) <br> **Default value:** `false`
|
| `roundTemp` | Round temperature value to nearest integer. <br><br> **Possible values:** `true` (round to integer) or `false` (display exact value with decimal point) <br> **Default value:** `false`
|
||||||
| `degreeLabel` | Show the degree label for your chosen units (Metric = C, Imperial = F, Kelvin = K). <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
|
| `degreeLabel` | Show the degree label for your chosen units (Metric = C, Imperial = F, Kelvin = K). <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
|
||||||
| `updateInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `600000` (10 minutes)
|
| `updateInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `600000` (10 minutes)
|
||||||
@@ -105,6 +107,14 @@ The following properties can be configured:
|
|||||||
| `lat` | The geo coordinate latitude. <br><br> This value is **REQUIRED**
|
| `lat` | The geo coordinate latitude. <br><br> This value is **REQUIRED**
|
||||||
| `lon` | The geo coordinate longitude. <br><br> This value is **REQUIRED**
|
| `lon` | The geo coordinate longitude. <br><br> This value is **REQUIRED**
|
||||||
|
|
||||||
|
### UK Met Office options
|
||||||
|
|
||||||
|
| Option | Description
|
||||||
|
| ---------------------------- | -----------
|
||||||
|
| `apiBase` | The UKMO base URL. <br><br> **Possible value:** `'http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/json/'` <br> This value is **REQUIRED**
|
||||||
|
| `locationId` | The UKMO API location code. <br><br> **Possible values:** `322942` <br> This value is **REQUIRED**
|
||||||
|
| `apiKey` | The [UK Met Office](https://www.metoffice.gov.uk/datapoint/getting-started) API key, which can be obtained by creating an UKMO Datapoint account. <br><br> This value is **REQUIRED**
|
||||||
|
|
||||||
## API Provider Development
|
## API Provider Development
|
||||||
|
|
||||||
If you want to add another API provider checkout the [Guide](providers).
|
If you want to add another API provider checkout the [Guide](providers).
|
||||||
|
17
modules/default/weather/current.njk
Normal file → Executable file
17
modules/default/weather/current.njk
Normal file → Executable file
@@ -24,7 +24,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
|
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
|
||||||
<span>
|
<span>
|
||||||
{% if current.nextSunAction() == "sunset" %}
|
{% if current.nextSunAction() === "sunset" %}
|
||||||
{{ current.sunset | formatTime }}
|
{{ current.sunset | formatTime }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ current.sunrise | formatTime }}
|
{{ current.sunrise | formatTime }}
|
||||||
@@ -56,11 +56,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if config.showFeelsLike and not config.onlyTemp %}
|
{% if (config.showFeelsLike or config.showPrecipitationAmount) and not config.onlyTemp %}
|
||||||
<div class="normal medium">
|
<div class="normal medium">
|
||||||
<span class="dimmed">
|
{% if config.showFeelsLike %}
|
||||||
{{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }}
|
<span class="dimmed">
|
||||||
</span>
|
{{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if config.showPrecipitationAmount %}
|
||||||
|
<span class="dimmed">
|
||||||
|
{{ "PRECIP" | translate }} {{ current.precipitation | unit("precip") }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
6
modules/default/weather/providers/README.md
Normal file → Executable file
6
modules/default/weather/providers/README.md
Normal file → Executable file
@@ -91,7 +91,9 @@ A convenience function to make requests. It returns a promise.
|
|||||||
|
|
||||||
| Property | Type | Value/Unit |
|
| Property | Type | Value/Unit |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric` and `imperial` |
|
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
|
||||||
|
| tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
|
||||||
|
| windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
|
||||||
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
|
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
|
||||||
| windSpeed |`number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
|
| windSpeed |`number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
|
||||||
| windDirection |`number` | Direction of the wind in degrees. |
|
| windDirection |`number` | Direction of the wind in degrees. |
|
||||||
@@ -104,7 +106,7 @@ A convenience function to make requests. It returns a promise.
|
|||||||
| humidity | `number` | Percentage of humidity |
|
| humidity | `number` | Percentage of humidity |
|
||||||
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
||||||
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
||||||
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` |
|
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` |
|
||||||
|
|
||||||
#### Current weather
|
#### Current weather
|
||||||
|
|
||||||
|
4
modules/default/weather/providers/darksky.js
Normal file → Executable file
4
modules/default/weather/providers/darksky.js
Normal file → Executable file
@@ -58,7 +58,7 @@ WeatherProvider.register("darksky", {
|
|||||||
|
|
||||||
// Implement WeatherDay generator.
|
// Implement WeatherDay generator.
|
||||||
generateWeatherDayFromCurrentWeather(currentWeatherData) {
|
generateWeatherDayFromCurrentWeather(currentWeatherData) {
|
||||||
const currentWeather = new WeatherObject(this.config.units);
|
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
currentWeather.date = moment();
|
currentWeather.date = moment();
|
||||||
currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity);
|
currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity);
|
||||||
@@ -76,7 +76,7 @@ WeatherProvider.register("darksky", {
|
|||||||
const days = [];
|
const days = [];
|
||||||
|
|
||||||
for (const forecast of forecasts) {
|
for (const forecast of forecasts) {
|
||||||
const weather = new WeatherObject(this.config.units);
|
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
weather.date = moment(forecast.time, "X");
|
weather.date = moment(forecast.time, "X");
|
||||||
weather.minTemperature = forecast.temperatureMin;
|
weather.minTemperature = forecast.temperatureMin;
|
||||||
|
14
modules/default/weather/providers/openweathermap.js
Normal file → Executable file
14
modules/default/weather/providers/openweathermap.js
Normal file → Executable file
@@ -68,7 +68,7 @@ WeatherProvider.register("openweathermap", {
|
|||||||
* Generate a WeatherObject based on currentWeatherInformation
|
* Generate a WeatherObject based on currentWeatherInformation
|
||||||
*/
|
*/
|
||||||
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||||
const currentWeather = new WeatherObject(this.config.units);
|
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
currentWeather.humidity = currentWeatherData.main.humidity;
|
currentWeather.humidity = currentWeatherData.main.humidity;
|
||||||
currentWeather.temperature = currentWeatherData.main.temp;
|
currentWeather.temperature = currentWeatherData.main.temp;
|
||||||
@@ -86,13 +86,13 @@ WeatherProvider.register("openweathermap", {
|
|||||||
*/
|
*/
|
||||||
generateWeatherObjectsFromForecast(forecasts) {
|
generateWeatherObjectsFromForecast(forecasts) {
|
||||||
|
|
||||||
if (this.config.weatherEndpoint == "/forecast") {
|
if (this.config.weatherEndpoint === "/forecast") {
|
||||||
return this.fetchForecastHourly(forecasts);
|
return this.fetchForecastHourly(forecasts);
|
||||||
} else if (this.config.weatherEndpoint == "/forecast/daily") {
|
} else if (this.config.weatherEndpoint === "/forecast/daily") {
|
||||||
return this.fetchForecastDaily(forecasts);
|
return this.fetchForecastDaily(forecasts);
|
||||||
}
|
}
|
||||||
// if weatherEndpoint does not match forecast or forecast/daily, what should be returned?
|
// if weatherEndpoint does not match forecast or forecast/daily, what should be returned?
|
||||||
const days = [new WeatherObject(this.config.units)];
|
const days = [new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits)];
|
||||||
return days;
|
return days;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ WeatherProvider.register("openweathermap", {
|
|||||||
let snow = 0;
|
let snow = 0;
|
||||||
// variable for date
|
// variable for date
|
||||||
let date = "";
|
let date = "";
|
||||||
let weather = new WeatherObject(this.config.units);
|
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
for (const forecast of forecasts) {
|
for (const forecast of forecasts) {
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ WeatherProvider.register("openweathermap", {
|
|||||||
// push weather information to days array
|
// push weather information to days array
|
||||||
days.push(weather);
|
days.push(weather);
|
||||||
// create new weather-object
|
// create new weather-object
|
||||||
weather = new WeatherObject(this.config.units);
|
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
minTemp = [];
|
minTemp = [];
|
||||||
maxTemp = [];
|
maxTemp = [];
|
||||||
@@ -187,7 +187,7 @@ WeatherProvider.register("openweathermap", {
|
|||||||
const days = [];
|
const days = [];
|
||||||
|
|
||||||
for (const forecast of forecasts) {
|
for (const forecast of forecasts) {
|
||||||
const weather = new WeatherObject(this.config.units);
|
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
weather.date = moment(forecast.dt, "X");
|
weather.date = moment(forecast.dt, "X");
|
||||||
weather.minTemperature = forecast.temp.min;
|
weather.minTemperature = forecast.temp.min;
|
||||||
|
262
modules/default/weather/providers/ukmetoffice.js
Executable file
262
modules/default/weather/providers/ukmetoffice.js
Executable file
@@ -0,0 +1,262 @@
|
|||||||
|
/* global WeatherProvider, WeatherObject */
|
||||||
|
|
||||||
|
/* Magic Mirror
|
||||||
|
* Module: Weather
|
||||||
|
*
|
||||||
|
* By Malcolm Oakes https://github.com/maloakes
|
||||||
|
* MIT Licensed.
|
||||||
|
*
|
||||||
|
* This class is a provider for UK Met Office Datapoint.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
WeatherProvider.register("ukmetoffice", {
|
||||||
|
|
||||||
|
// Set the name of the provider.
|
||||||
|
// This isn't strictly necessary, since it will fallback to the provider identifier
|
||||||
|
// But for debugging (and future alerts) it would be nice to have the real name.
|
||||||
|
providerName: "UK Met Office",
|
||||||
|
|
||||||
|
units: {
|
||||||
|
imperial: "us",
|
||||||
|
metric: "si"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Overwrite the fetchCurrentWeather method.
|
||||||
|
fetchCurrentWeather() {
|
||||||
|
this.fetchData(this.getUrl("3hourly"))
|
||||||
|
.then(data => {
|
||||||
|
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location ||
|
||||||
|
!data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length == 0) {
|
||||||
|
// Did not receive usable new data.
|
||||||
|
// Maybe this needs a better check?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setFetchedLocation(`${data.SiteRep.DV.Location.name}, ${data.SiteRep.DV.Location.country}`);
|
||||||
|
|
||||||
|
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
|
||||||
|
this.setCurrentWeather(currentWeather);
|
||||||
|
})
|
||||||
|
.catch(function(request) {
|
||||||
|
Log.error("Could not load data ... ", request);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// Overwrite the fetchCurrentWeather method.
|
||||||
|
fetchWeatherForecast() {
|
||||||
|
this.fetchData(this.getUrl("daily"))
|
||||||
|
.then(data => {
|
||||||
|
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location ||
|
||||||
|
!data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length == 0) {
|
||||||
|
// Did not receive usable new data.
|
||||||
|
// Maybe this needs a better check?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setFetchedLocation(`${data.SiteRep.DV.Location.name}, ${data.SiteRep.DV.Location.country}`);
|
||||||
|
|
||||||
|
const forecast = this.generateWeatherObjectsFromForecast(data);
|
||||||
|
this.setWeatherForecast(forecast);
|
||||||
|
})
|
||||||
|
.catch(function(request) {
|
||||||
|
Log.error("Could not load data ... ", request);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** UK Met Office Specific Methods - These are not part of the default provider methods */
|
||||||
|
/*
|
||||||
|
* Gets the complete url for the request
|
||||||
|
*/
|
||||||
|
getUrl(forecastType) {
|
||||||
|
return this.config.apiBase + this.config.locationID + this.getParams(forecastType);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate a WeatherObject based on currentWeatherInformation
|
||||||
|
*/
|
||||||
|
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||||
|
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
|
// data times are always UTC
|
||||||
|
let nowUtc = moment.utc()
|
||||||
|
let midnightUtc = nowUtc.clone().startOf("day")
|
||||||
|
let timeInMins = nowUtc.diff(midnightUtc, "minutes");
|
||||||
|
|
||||||
|
// loop round each of the (5) periods, look for today (the first period may be yesterday)
|
||||||
|
for (i in currentWeatherData.SiteRep.DV.Location.Period) {
|
||||||
|
let periodDate = moment.utc(currentWeatherData.SiteRep.DV.Location.Period[i].value.substr(0,10), "YYYY-MM-DD")
|
||||||
|
|
||||||
|
// ignore if period is before today
|
||||||
|
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
|
||||||
|
|
||||||
|
// check this is the period we want, after today the diff will be -ve
|
||||||
|
if (moment().diff(periodDate, "minutes") > 0) {
|
||||||
|
// loop round the reports looking for the one we are in
|
||||||
|
// $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
|
||||||
|
for (j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep){
|
||||||
|
let p = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].$;
|
||||||
|
if (timeInMins >= p && timeInMins-180 < p) {
|
||||||
|
// finally got the one we want, so populate weather object
|
||||||
|
currentWeather.humidity = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].H;
|
||||||
|
currentWeather.temperature = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].T);
|
||||||
|
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].F);
|
||||||
|
currentWeather.precipitation = parseInt(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].Pp);
|
||||||
|
currentWeather.windSpeed = this.convertWindSpeed(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].S);
|
||||||
|
currentWeather.windDirection = this.convertWindDirection(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].D);
|
||||||
|
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].W);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the sunrise/sunset times - not supplied in UK Met Office data
|
||||||
|
let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location)
|
||||||
|
currentWeather.sunrise = times[0];
|
||||||
|
currentWeather.sunset = times[1];
|
||||||
|
|
||||||
|
return currentWeather;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate WeatherObjects based on forecast information
|
||||||
|
*/
|
||||||
|
generateWeatherObjectsFromForecast(forecasts) {
|
||||||
|
|
||||||
|
const days = [];
|
||||||
|
|
||||||
|
// loop round the (5) periods getting the data
|
||||||
|
// for each period array, Day is [0], Night is [1]
|
||||||
|
for (j in forecasts.SiteRep.DV.Location.Period) {
|
||||||
|
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
|
// data times are always UTC
|
||||||
|
dateStr = forecasts.SiteRep.DV.Location.Period[j].value
|
||||||
|
let periodDate = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD")
|
||||||
|
|
||||||
|
// ignore if period is before today
|
||||||
|
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
|
||||||
|
// populate the weather object
|
||||||
|
weather.date = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD");
|
||||||
|
weather.minTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[1].Nm);
|
||||||
|
weather.maxTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[0].Dm);
|
||||||
|
weather.weatherType = this.convertWeatherType(forecasts.SiteRep.DV.Location.Period[j].Rep[0].W);
|
||||||
|
weather.precipitation = parseInt(forecasts.SiteRep.DV.Location.Period[j].Rep[0].PPd);
|
||||||
|
|
||||||
|
days.push(weather);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return days;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* calculate the astronomical data
|
||||||
|
*/
|
||||||
|
calcAstroData(location) {
|
||||||
|
const sunTimes = [];
|
||||||
|
|
||||||
|
// determine the sunrise/sunset times
|
||||||
|
let times = SunCalc.getTimes(new Date(), location.lat, location.lon);
|
||||||
|
sunTimes.push(moment(times.sunrise, "X"));
|
||||||
|
sunTimes.push(moment(times.sunset, "X"));
|
||||||
|
|
||||||
|
return sunTimes;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert the Met Office icons to a more usable name.
|
||||||
|
*/
|
||||||
|
convertWeatherType(weatherType) {
|
||||||
|
const weatherTypes = {
|
||||||
|
0: "night-clear",
|
||||||
|
1: "day-sunny",
|
||||||
|
2: "night-alt-cloudy",
|
||||||
|
3: "day-cloudy",
|
||||||
|
5: "fog",
|
||||||
|
6: "fog",
|
||||||
|
7: "cloudy",
|
||||||
|
8: "cloud",
|
||||||
|
9: "night-sprinkle",
|
||||||
|
10: "day-sprinkle",
|
||||||
|
11: "raindrops",
|
||||||
|
12: "sprinkle",
|
||||||
|
13: "night-alt-showers",
|
||||||
|
14: "day-showers",
|
||||||
|
15: "rain",
|
||||||
|
16: "night-alt-sleet",
|
||||||
|
17: "day-sleet",
|
||||||
|
18: "sleet",
|
||||||
|
19: "night-alt-hail",
|
||||||
|
20: "day-hail",
|
||||||
|
21: "hail",
|
||||||
|
22: "night-alt-snow",
|
||||||
|
23: "day-snow",
|
||||||
|
24: "snow",
|
||||||
|
25: "night-alt-snow",
|
||||||
|
26: "day-snow",
|
||||||
|
27: "snow",
|
||||||
|
28: "night-alt-thunderstorm",
|
||||||
|
29: "day-thunderstorm",
|
||||||
|
30: "thunderstorm"
|
||||||
|
};
|
||||||
|
|
||||||
|
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert temp (from degrees C) if required
|
||||||
|
*/
|
||||||
|
convertTemp(tempInC) {
|
||||||
|
return this.tempUnits === "imperial" ? tempInC * 9 / 5 + 32 : tempInC;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert wind speed (from mph) if required
|
||||||
|
*/
|
||||||
|
convertWindSpeed(windInMph) {
|
||||||
|
return this.windUnits === "metric" ? windInMph * 2.23694 : windInMph;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert the wind direction cardinal to value
|
||||||
|
*/
|
||||||
|
convertWindDirection(windDirection) {
|
||||||
|
const windCardinals = {
|
||||||
|
"N": 0,
|
||||||
|
"NNE": 22,
|
||||||
|
"NE": 45,
|
||||||
|
"ENE": 67,
|
||||||
|
"E": 90,
|
||||||
|
"ESE": 112,
|
||||||
|
"SE": 135,
|
||||||
|
"SSE": 157,
|
||||||
|
"S": 180,
|
||||||
|
"SSW": 202,
|
||||||
|
"SW": 225,
|
||||||
|
"WSW": 247,
|
||||||
|
"W": 270,
|
||||||
|
"WNW": 292,
|
||||||
|
"NW": 315,
|
||||||
|
"NNW": 337
|
||||||
|
};
|
||||||
|
|
||||||
|
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generates an url with api parameters based on the config.
|
||||||
|
*
|
||||||
|
* return String - URL params.
|
||||||
|
*/
|
||||||
|
getParams(forecastType) {
|
||||||
|
let params = "?";
|
||||||
|
params += "res=" + forecastType;
|
||||||
|
params += "&key=" + this.config.apiKey;
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
});
|
6
modules/default/weather/providers/weathergov.js
Normal file → Executable file
6
modules/default/weather/providers/weathergov.js
Normal file → Executable file
@@ -67,7 +67,7 @@ WeatherProvider.register("weathergov", {
|
|||||||
* Generate a WeatherObject based on currentWeatherInformation
|
* Generate a WeatherObject based on currentWeatherInformation
|
||||||
*/
|
*/
|
||||||
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||||
const currentWeather = new WeatherObject(this.config.units);
|
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
currentWeather.temperature = currentWeatherData.temperature;
|
currentWeather.temperature = currentWeatherData.temperature;
|
||||||
currentWeather.windSpeed = currentWeatherData.windSpeed.split(" ", 1);
|
currentWeather.windSpeed = currentWeatherData.windSpeed.split(" ", 1);
|
||||||
@@ -95,7 +95,7 @@ WeatherProvider.register("weathergov", {
|
|||||||
let maxTemp = [];
|
let maxTemp = [];
|
||||||
// variable for date
|
// variable for date
|
||||||
let date = "";
|
let date = "";
|
||||||
let weather = new WeatherObject(this.config.units);
|
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
weather.precipitation = 0;
|
weather.precipitation = 0;
|
||||||
|
|
||||||
for (const forecast of forecasts) {
|
for (const forecast of forecasts) {
|
||||||
@@ -109,7 +109,7 @@ WeatherProvider.register("weathergov", {
|
|||||||
// push weather information to days array
|
// push weather information to days array
|
||||||
days.push(weather);
|
days.push(weather);
|
||||||
// create new weather-object
|
// create new weather-object
|
||||||
weather = new WeatherObject(this.config.units);
|
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||||
|
|
||||||
minTemp = [];
|
minTemp = [];
|
||||||
maxTemp = [];
|
maxTemp = [];
|
||||||
|
24
modules/default/weather/weather.js
Normal file → Executable file
24
modules/default/weather/weather.js
Normal file → Executable file
@@ -19,6 +19,10 @@ Module.register("weather",{
|
|||||||
locationID: false,
|
locationID: false,
|
||||||
appid: "",
|
appid: "",
|
||||||
units: config.units,
|
units: config.units,
|
||||||
|
|
||||||
|
tempUnits: config.units,
|
||||||
|
windUnits: config.units,
|
||||||
|
|
||||||
updateInterval: 10 * 60 * 1000, // every 10 minutes
|
updateInterval: 10 * 60 * 1000, // every 10 minutes
|
||||||
animationSpeed: 1000,
|
animationSpeed: 1000,
|
||||||
timeFormat: config.timeFormat,
|
timeFormat: config.timeFormat,
|
||||||
@@ -68,13 +72,14 @@ Module.register("weather",{
|
|||||||
"moment.js",
|
"moment.js",
|
||||||
"weatherprovider.js",
|
"weatherprovider.js",
|
||||||
"weatherobject.js",
|
"weatherobject.js",
|
||||||
|
"suncalc.js",
|
||||||
this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")
|
this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
// Override getHeader method.
|
// Override getHeader method.
|
||||||
getHeader: function() {
|
getHeader: function() {
|
||||||
if (this.config.appendLocationNameToHeader && this.weatherProvider) {
|
if (this.config.appendLocationNameToHeader && this.data.header !== undefined && this.weatherProvider) {
|
||||||
return this.data.header + " " + this.weatherProvider.fetchedLocation();
|
return this.data.header + " " + this.weatherProvider.fetchedLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +89,7 @@ Module.register("weather",{
|
|||||||
// Start the weather module.
|
// Start the weather module.
|
||||||
start: function () {
|
start: function () {
|
||||||
moment.locale(this.config.lang);
|
moment.locale(this.config.lang);
|
||||||
|
|
||||||
// Initialize the weather provider.
|
// Initialize the weather provider.
|
||||||
this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this);
|
this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this);
|
||||||
|
|
||||||
@@ -137,7 +143,7 @@ Module.register("weather",{
|
|||||||
humidity: this.indoorHumidity,
|
humidity: this.indoorHumidity,
|
||||||
temperature: this.indoorTemperature
|
temperature: this.indoorTemperature
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// What to do when the weather provider has new information available?
|
// What to do when the weather provider has new information available?
|
||||||
@@ -188,13 +194,13 @@ Module.register("weather",{
|
|||||||
|
|
||||||
this.nunjucksEnvironment().addFilter("unit", function (value, type) {
|
this.nunjucksEnvironment().addFilter("unit", function (value, type) {
|
||||||
if (type === "temperature") {
|
if (type === "temperature") {
|
||||||
if (this.config.units === "metric" || this.config.units === "imperial") {
|
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
|
||||||
value += "°";
|
value += "°";
|
||||||
}
|
}
|
||||||
if (this.config.degreeLabel) {
|
if (this.config.degreeLabel) {
|
||||||
if (this.config.units === "metric") {
|
if (this.config.tempUnits === "metric") {
|
||||||
value += "C";
|
value += "C";
|
||||||
} else if (this.config.units === "imperial") {
|
} else if (this.config.tempUnits === "imperial") {
|
||||||
value += "F";
|
value += "F";
|
||||||
} else {
|
} else {
|
||||||
value += "K";
|
value += "K";
|
||||||
@@ -204,10 +210,14 @@ Module.register("weather",{
|
|||||||
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
|
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
|
||||||
value = "";
|
value = "";
|
||||||
} else {
|
} else {
|
||||||
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
|
if (this.config.weatherProvider === "ukmetoffice") {
|
||||||
|
value += "%";
|
||||||
|
} else {
|
||||||
|
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (type === "humidity") {
|
} else if (type === "humidity") {
|
||||||
value += "%"
|
value += "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
18
modules/default/weather/weatherobject.js
Normal file → Executable file
18
modules/default/weather/weatherobject.js
Normal file → Executable file
@@ -13,8 +13,11 @@
|
|||||||
// As soon as we start implementing the forecast, mode properties will be added.
|
// As soon as we start implementing the forecast, mode properties will be added.
|
||||||
|
|
||||||
class WeatherObject {
|
class WeatherObject {
|
||||||
constructor(units) {
|
constructor(units, tempUnits, windUnits) {
|
||||||
|
|
||||||
this.units = units;
|
this.units = units;
|
||||||
|
this.tempUnits = tempUnits;
|
||||||
|
this.windUnits = windUnits;
|
||||||
this.date = null;
|
this.date = null;
|
||||||
this.windSpeed = null;
|
this.windSpeed = null;
|
||||||
this.windDirection = null;
|
this.windDirection = null;
|
||||||
@@ -28,6 +31,8 @@ class WeatherObject {
|
|||||||
this.rain = null;
|
this.rain = null;
|
||||||
this.snow = null;
|
this.snow = null;
|
||||||
this.precipitation = null;
|
this.precipitation = null;
|
||||||
|
this.feelsLikeTemp = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cardinalWindDirection() {
|
cardinalWindDirection() {
|
||||||
@@ -67,7 +72,7 @@ class WeatherObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beaufortWindSpeed() {
|
beaufortWindSpeed() {
|
||||||
const windInKmh = this.units === "imperial" ? this.windSpeed * 1.609344 : this.windSpeed * 60 * 60 / 1000;
|
const windInKmh = (this.windUnits === "imperial") ? this.windSpeed * 1.609344 : this.windSpeed * 60 * 60 / 1000;
|
||||||
const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
|
const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
|
||||||
for (const [index, speed] of speeds.entries()) {
|
for (const [index, speed] of speeds.entries()) {
|
||||||
if (speed > windInKmh) {
|
if (speed > windInKmh) {
|
||||||
@@ -82,8 +87,11 @@ class WeatherObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
feelsLike() {
|
feelsLike() {
|
||||||
const windInMph = this.units === "imperial" ? this.windSpeed : this.windSpeed * 2.23694;
|
if (this.feelsLikeTemp) {
|
||||||
const tempInF = this.units === "imperial" ? this.temperature : this.temperature * 9 / 5 + 32;
|
return this.feelsLikeTemp;
|
||||||
|
}
|
||||||
|
const windInMph = (this.windUnits === "imperial") ? this.windSpeed : this.windSpeed * 2.23694;
|
||||||
|
const tempInF = this.tempUnits === "imperial" ? this.temperature : this.temperature * 9 / 5 + 32;
|
||||||
let feelsLike = tempInF;
|
let feelsLike = tempInF;
|
||||||
|
|
||||||
if (windInMph > 3 && tempInF < 50) {
|
if (windInMph > 3 && tempInF < 50) {
|
||||||
@@ -97,6 +105,6 @@ class WeatherObject {
|
|||||||
- 1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
|
- 1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.units === "imperial" ? feelsLike : (feelsLike - 32) * 5 / 9;
|
return this.tempUnits === "imperial" ? feelsLike : (feelsLike - 32) * 5 / 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,6 @@
|
|||||||
* This class is the blueprint for a weather provider.
|
* This class is the blueprint for a weather provider.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base BluePrint for the WeatherProvider
|
* Base BluePrint for the WeatherProvider
|
||||||
*/
|
*/
|
||||||
@@ -23,15 +22,14 @@ var WeatherProvider = Class.extend({
|
|||||||
weatherForecastArray: null,
|
weatherForecastArray: null,
|
||||||
fetchedLocationName: null,
|
fetchedLocationName: null,
|
||||||
|
|
||||||
// The following properties will be set automaticly.
|
// The following properties will be set automatically.
|
||||||
// You do not need to overwrite these properties.
|
// You do not need to overwrite these properties.
|
||||||
config: null,
|
config: null,
|
||||||
delegate: null,
|
delegate: null,
|
||||||
providerIdentifier: null,
|
providerIdentifier: null,
|
||||||
|
|
||||||
|
|
||||||
// Weather Provider Methods
|
// Weather Provider Methods
|
||||||
// All the following methods can be overwrited, although most are good as they are.
|
// All the following methods can be overwritten, although most are good as they are.
|
||||||
|
|
||||||
// Called when a weather provider is initialized.
|
// Called when a weather provider is initialized.
|
||||||
init: function(config) {
|
init: function(config) {
|
||||||
@@ -51,13 +49,13 @@ var WeatherProvider = Class.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// This method should start the API request to fetch the current weather.
|
// This method should start the API request to fetch the current weather.
|
||||||
// This method should definetly be overwritten in the provider.
|
// This method should definitely be overwritten in the provider.
|
||||||
fetchCurrentWeather: function() {
|
fetchCurrentWeather: function() {
|
||||||
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchCurrentWeather method.`);
|
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchCurrentWeather method.`);
|
||||||
},
|
},
|
||||||
|
|
||||||
// This method should start the API request to fetch the weather forecast.
|
// This method should start the API request to fetch the weather forecast.
|
||||||
// This method should definetly be overwritten in the provider.
|
// This method should definitely be overwritten in the provider.
|
||||||
fetchWeatherForecast: function() {
|
fetchWeatherForecast: function() {
|
||||||
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`);
|
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`);
|
||||||
},
|
},
|
||||||
@@ -103,7 +101,7 @@ var WeatherProvider = Class.extend({
|
|||||||
this.delegate.updateAvailable(this);
|
this.delegate.updateAvailable(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
// A convinience function to make requests. It returns a promise.
|
// A convenience function to make requests. It returns a promise.
|
||||||
fetchData: function(url, method = "GET", data = null) {
|
fetchData: function(url, method = "GET", data = null) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
@@ -113,12 +111,12 @@ var WeatherProvider = Class.extend({
|
|||||||
if (this.status === 200) {
|
if (this.status === 200) {
|
||||||
resolve(JSON.parse(this.response));
|
resolve(JSON.parse(this.response));
|
||||||
} else {
|
} else {
|
||||||
reject(request)
|
reject(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
request.send();
|
request.send();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -19,9 +19,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.weatherforecast tr.colored .min-temp {
|
.weatherforecast tr.colored .min-temp {
|
||||||
color: #BCDDFF;
|
color: #BCDDFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.weatherforecast tr.colored .max-temp {
|
.weatherforecast tr.colored .max-temp {
|
||||||
color: #FF8E99;
|
color: #FF8E99;
|
||||||
}
|
}
|
||||||
|
@@ -82,7 +82,7 @@ Module.register("weatherforecast",{
|
|||||||
getTranslations: function() {
|
getTranslations: function() {
|
||||||
// The translations for the default modules are defined in the core translation files.
|
// The translations for the default modules are defined in the core translation files.
|
||||||
// Therefor we can just return false. Otherwise we should have returned a dictionary.
|
// Therefor we can just return false. Otherwise we should have returned a dictionary.
|
||||||
// If you're trying to build yiur own module including translations, check out the documentation.
|
// If you're trying to build your own module including translations, check out the documentation.
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ Module.register("weatherforecast",{
|
|||||||
|
|
||||||
/* updateWeather(compliments)
|
/* updateWeather(compliments)
|
||||||
* Requests new data from openweather.org.
|
* Requests new data from openweather.org.
|
||||||
* Calls processWeather on succesfull response.
|
* Calls processWeather on successful response.
|
||||||
*/
|
*/
|
||||||
updateWeather: function() {
|
updateWeather: function() {
|
||||||
if (this.config.appid === "") {
|
if (this.config.appid === "") {
|
||||||
@@ -261,7 +261,7 @@ Module.register("weatherforecast",{
|
|||||||
} else if (this.status === 401) {
|
} else if (this.status === 401) {
|
||||||
self.updateDom(self.config.animationSpeed);
|
self.updateDom(self.config.animationSpeed);
|
||||||
|
|
||||||
if (self.config.forecastEndpoint == "forecast/daily") {
|
if (self.config.forecastEndpoint === "forecast/daily") {
|
||||||
self.config.forecastEndpoint = "forecast";
|
self.config.forecastEndpoint = "forecast";
|
||||||
Log.warn(self.name + ": Your AppID does not support long term forecasts. Switching to fallback endpoint.");
|
Log.warn(self.name + ": Your AppID does not support long term forecasts. Switching to fallback endpoint.");
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ Module.register("weatherforecast",{
|
|||||||
} 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) {
|
||||||
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon
|
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
|
||||||
} else if (this.firstEvent && this.firstEvent.location) {
|
} else if (this.firstEvent && this.firstEvent.location) {
|
||||||
params += "q=" + this.firstEvent.location;
|
params += "q=" + this.firstEvent.location;
|
||||||
} else {
|
} else {
|
||||||
@@ -315,7 +315,7 @@ Module.register("weatherforecast",{
|
|||||||
*/
|
*/
|
||||||
parserDataWeather: function(data) {
|
parserDataWeather: function(data) {
|
||||||
if (data.hasOwnProperty("main")) {
|
if (data.hasOwnProperty("main")) {
|
||||||
data["temp"] = {"min": data.main.temp_min, "max": data.main.temp_max}
|
data["temp"] = {"min": data.main.temp_min, "max": data.main.temp_max};
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
@@ -330,7 +330,7 @@ Module.register("weatherforecast",{
|
|||||||
|
|
||||||
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++) {
|
for (var i = 0, count = data.list.length; i < count; i++) {
|
||||||
|
|
||||||
|
6576
package-lock.json
generated
6576
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -41,37 +41,34 @@
|
|||||||
"grunt": "latest",
|
"grunt": "latest",
|
||||||
"grunt-eslint": "latest",
|
"grunt-eslint": "latest",
|
||||||
"grunt-jsonlint": "latest",
|
"grunt-jsonlint": "latest",
|
||||||
"grunt-markdownlint": "^1.0.43",
|
"grunt-markdownlint": "latest",
|
||||||
"grunt-stylelint": "latest",
|
"grunt-stylelint": "latest",
|
||||||
"grunt-yamllint": "latest",
|
"grunt-yamllint": "latest",
|
||||||
"http-auth": "^3.2.3",
|
"http-auth": "^3.2.3",
|
||||||
"jsdom": "^11.6.2",
|
"jsdom": "^11.6.2",
|
||||||
"jshint": "^2.9.5",
|
"jshint": "^2.10.2",
|
||||||
"mocha": "^4.1.0",
|
"mocha": "^4.1.0",
|
||||||
"mocha-each": "^1.1.0",
|
"mocha-each": "^1.1.0",
|
||||||
|
"mocha-logger": "^1.0.6",
|
||||||
"spectron": "^3.8.0",
|
"spectron": "^3.8.0",
|
||||||
"stylelint": "^8.4.0",
|
"stylelint": "latest",
|
||||||
"stylelint-config-standard": "latest",
|
"stylelint-config-standard": "latest",
|
||||||
"time-grunt": "latest"
|
"time-grunt": "latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "6.5.5",
|
|
||||||
"body-parser": "^1.18.2",
|
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
"electron": "^3.0.13",
|
"electron": "^3.0.13",
|
||||||
"express": "^4.16.2",
|
"express": "^4.16.2",
|
||||||
"express-ipfilter": "0.3.1",
|
"express-ipfilter": "^1.0.1",
|
||||||
"feedme": "latest",
|
"feedme": "latest",
|
||||||
"helmet": "^3.9.0",
|
"helmet": "^3.9.0",
|
||||||
"home-path": "^1.0.6",
|
|
||||||
"iconv-lite": "latest",
|
"iconv-lite": "latest",
|
||||||
"mocha-logger": "^1.0.6",
|
|
||||||
"moment": "latest",
|
"moment": "latest",
|
||||||
"request": "^2.87.0",
|
"request": "^2.88.0",
|
||||||
|
"rrule": "^2.6.2",
|
||||||
"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",
|
||||||
"valid-url": "latest",
|
"valid-url": "latest"
|
||||||
"walk": "latest"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@ var Utils = require(__dirname + "/../../js/utils.js");
|
|||||||
|
|
||||||
/* getConfigFile()
|
/* getConfigFile()
|
||||||
* Return string with path of configuration file
|
* Return string with path of configuration file
|
||||||
* Check if set by enviroment variable MM_CONFIG_FILE
|
* Check if set by environment variable MM_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!
|
||||||
@@ -35,7 +35,7 @@ function checkConfigFile() {
|
|||||||
console.error(Utils.colors.error("File not found: "), configFileName);
|
console.error(Utils.colors.error("File not found: "), configFileName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// check permision
|
// check permission
|
||||||
try {
|
try {
|
||||||
fs.accessSync(configFileName, fs.F_OK);
|
fs.accessSync(configFileName, fs.F_OK);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -52,12 +52,12 @@ function checkConfigFile() {
|
|||||||
if (err) { throw err; }
|
if (err) { throw err; }
|
||||||
v.JSHINT(data); // Parser by jshint
|
v.JSHINT(data); // Parser by jshint
|
||||||
|
|
||||||
if (v.JSHINT.errors.length == 0) {
|
if (v.JSHINT.errors.length === 0) {
|
||||||
console.log("Your configuration file doesn't contain syntax errors :)");
|
console.log("Your configuration file doesn't contain syntax errors :)");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
errors = v.JSHINT.data().errors;
|
errors = v.JSHINT.data().errors;
|
||||||
for (idx in errors) {
|
for (var idx in errors) {
|
||||||
error = errors[idx];
|
error = errors[idx];
|
||||||
console.log("Line", error.line, "col", error.character, error.reason);
|
console.log("Line", error.line, "col", error.character, error.reason);
|
||||||
}
|
}
|
||||||
@@ -67,4 +67,4 @@ function checkConfigFile() {
|
|||||||
|
|
||||||
if (process.env.NODE_ENV !== "test") {
|
if (process.env.NODE_ENV !== "test") {
|
||||||
checkConfigFile();
|
checkConfigFile();
|
||||||
};
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
/* Magic Mirror Test config sample enviroment
|
/* Magic Mirror Test config sample environment
|
||||||
*
|
*
|
||||||
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
|
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
port: 8080,
|
port: 8080,
|
||||||
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
|
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
/* Magic Mirror Test config sample enviroment set por 8090
|
/* Magic Mirror Test config sample environment set port 8090
|
||||||
*
|
*
|
||||||
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
|
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
|
||||||
* MIT Licensed.
|
* MIT Licensed.
|
||||||
|
@@ -1,13 +1,8 @@
|
|||||||
const helpers = require("./global-setup");
|
const helpers = require("./global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
const it = global.it;
|
||||||
const beforeEach = global.beforeEach;
|
|
||||||
const afterEach = global.afterEach;
|
|
||||||
|
|
||||||
describe("Development console tests", function() {
|
describe("Development console tests", function() {
|
||||||
// This tests fail and crash another tests
|
// This tests fail and crash another tests
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
const helpers = require("./global-setup");
|
const helpers = require("./global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
|
@@ -1,14 +1,9 @@
|
|||||||
const helpers = require("./global-setup");
|
const helpers = require("./global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
const forEach = require("mocha-each");
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
|
||||||
const beforeEach = global.beforeEach;
|
|
||||||
const afterEach = global.afterEach;
|
|
||||||
const forEach = require("mocha-each");
|
|
||||||
|
|
||||||
describe("All font files from roboto.css should be downloadable", function() {
|
describe("All font files from roboto.css should be downloadable", function() {
|
||||||
helpers.setupTimeout(this);
|
helpers.setupTimeout(this);
|
||||||
@@ -18,7 +13,7 @@ describe("All font files from roboto.css should be downloadable", function() {
|
|||||||
var fileContent = require("fs").readFileSync(__dirname + "/../../fonts/roboto.css", "utf8");
|
var fileContent = require("fs").readFileSync(__dirname + "/../../fonts/roboto.css", "utf8");
|
||||||
var regex = /\burl\(['"]([^'"]+)['"]\)/g;
|
var regex = /\burl\(['"]([^'"]+)['"]\)/g;
|
||||||
var match = regex.exec(fileContent);
|
var match = regex.exec(fileContent);
|
||||||
while (match != null) {
|
while (match !== null) {
|
||||||
// Push 1st match group onto fontFiles stack
|
// Push 1st match group onto fontFiles stack
|
||||||
fontFiles.push(match[1]);
|
fontFiles.push(match[1]);
|
||||||
// Find the next one
|
// Find the next one
|
||||||
|
@@ -12,7 +12,6 @@ const Application = require("spectron").Application;
|
|||||||
const assert = require("assert");
|
const assert = require("assert");
|
||||||
const chai = require("chai");
|
const chai = require("chai");
|
||||||
const chaiAsPromised = require("chai-as-promised");
|
const chaiAsPromised = require("chai-as-promised");
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
global.before(function() {
|
global.before(function() {
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
const helpers = require("./global-setup");
|
const helpers = require("./global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
@@ -17,7 +15,7 @@ describe("ipWhitelist directive configuration", function () {
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return helpers.startApplication({
|
return helpers.startApplication({
|
||||||
args: ["js/electron.js"]
|
args: ["js/electron.js"]
|
||||||
}).then(function (startedApp) { app = startedApp; })
|
}).then(function (startedApp) { app = startedApp; });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
@@ -1,10 +1,6 @@
|
|||||||
const helpers = require("../global-setup");
|
const helpers = require("../global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
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;
|
||||||
const beforeEach = global.beforeEach;
|
const beforeEach = global.beforeEach;
|
||||||
@@ -72,7 +68,7 @@ describe("Calendar module", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Basic auth backward compatibilty configuration: DEPRECATED", function() {
|
describe("Basic auth backward compatibility configuration: DEPRECATED", function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
serverBasicAuth.listen(8012);
|
serverBasicAuth.listen(8012);
|
||||||
// Set config sample for use in test
|
// Set config sample for use in test
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
const helpers = require("../global-setup");
|
const helpers = require("../global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
const it = global.it;
|
||||||
@@ -86,5 +82,4 @@ describe("Clock set to spanish language module", function() {
|
|||||||
.getText(".clock .week").should.eventually.match(weekRegex);
|
.getText(".clock .week").should.eventually.match(weekRegex);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
const helpers = require("../global-setup");
|
const helpers = require("../global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
const it = global.it;
|
||||||
|
@@ -1,7 +1,4 @@
|
|||||||
const helpers = require("../global-setup");
|
const helpers = require("../global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
const helpers = require("../global-setup");
|
const helpers = require("../global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
const it = global.it;
|
||||||
@@ -24,7 +20,6 @@ describe("Test helloworld module", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
return helpers.stopApplication(app);
|
return helpers.stopApplication(app);
|
||||||
});
|
});
|
||||||
@@ -52,5 +47,4 @@ describe("Test helloworld module", function() {
|
|||||||
.getText(".helloworld").should.eventually.equal("Hello World!");
|
.getText(".helloworld").should.eventually.equal("Hello World!");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
const helpers = require("../global-setup");
|
const helpers = require("../global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
const it = global.it;
|
||||||
|
@@ -1,13 +1,7 @@
|
|||||||
const helpers = require("./global-setup");
|
const helpers = require("./global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
const it = global.it;
|
||||||
const beforeEach = global.beforeEach;
|
|
||||||
const afterEach = global.afterEach;
|
|
||||||
|
|
||||||
describe("Position of modules", function () {
|
describe("Position of modules", function () {
|
||||||
helpers.setupTimeout(this);
|
helpers.setupTimeout(this);
|
||||||
@@ -25,8 +19,7 @@ describe("Position of modules", function () {
|
|||||||
process.env.MM_CONFIG_FILE = "tests/configs/modules/positions.js";
|
process.env.MM_CONFIG_FILE = "tests/configs/modules/positions.js";
|
||||||
return helpers.startApplication({
|
return helpers.startApplication({
|
||||||
args: ["js/electron.js"]
|
args: ["js/electron.js"]
|
||||||
}).then(function (startedApp) { app = startedApp; })
|
}).then(function (startedApp) { app = startedApp; });
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third",
|
var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third",
|
||||||
@@ -44,5 +37,4 @@ describe("Position of modules", function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
const helpers = require("./global-setup");
|
const helpers = require("./global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
@@ -17,7 +15,7 @@ describe("port directive configuration", function () {
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return helpers.startApplication({
|
return helpers.startApplication({
|
||||||
args: ["js/electron.js"]
|
args: ["js/electron.js"]
|
||||||
}).then(function (startedApp) { app = startedApp; })
|
}).then(function (startedApp) { app = startedApp; });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -38,7 +36,7 @@ describe("port directive configuration", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Set port 8100 on enviroment variable MM_PORT", function () {
|
describe("Set port 8100 on environment variable MM_PORT", function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
process.env.MM_PORT = 8100;
|
process.env.MM_PORT = 8100;
|
||||||
// Set config sample for use in this test
|
// Set config sample for use in this test
|
||||||
@@ -56,5 +54,4 @@ describe("port directive configuration", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -121,8 +121,7 @@ describe("Translations", function() {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
const helpers = require("./global-setup");
|
const helpers = require("./global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
const expect = require("chai").expect;
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
@@ -20,7 +18,7 @@ describe("Vendors", function () {
|
|||||||
before(function () {
|
before(function () {
|
||||||
return helpers.startApplication({
|
return helpers.startApplication({
|
||||||
args: ["js/electron.js"]
|
args: ["js/electron.js"]
|
||||||
}).then(function (startedApp) { app = startedApp; })
|
}).then(function (startedApp) { app = startedApp; });
|
||||||
});
|
});
|
||||||
|
|
||||||
after(function () {
|
after(function () {
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
const helpers = require("./global-setup");
|
const helpers = require("./global-setup");
|
||||||
const path = require("path");
|
|
||||||
const request = require("request");
|
|
||||||
|
|
||||||
const expect = require("chai").expect;
|
|
||||||
|
|
||||||
const describe = global.describe;
|
const describe = global.describe;
|
||||||
const it = global.it;
|
const it = global.it;
|
||||||
@@ -17,7 +13,7 @@ describe("Check configuration without modules", function () {
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return helpers.startApplication({
|
return helpers.startApplication({
|
||||||
args: ["js/electron.js"]
|
args: ["js/electron.js"]
|
||||||
}).then(function (startedApp) { app = startedApp; })
|
}).then(function (startedApp) { app = startedApp; });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -31,13 +27,12 @@ describe("Check configuration without modules", function () {
|
|||||||
|
|
||||||
it("Show the message MagicMirror title", function () {
|
it("Show the message MagicMirror title", function () {
|
||||||
return app.client.waitUntilWindowLoaded()
|
return app.client.waitUntilWindowLoaded()
|
||||||
.getText("#module_1_helloworld .module-content").should.eventually.equal("Magic Mirror2")
|
.getText("#module_1_helloworld .module-content").should.eventually.equal("Magic Mirror2");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Show the text Michael's website", function () {
|
it("Show the text Michael's website", function () {
|
||||||
return app.client.waitUntilWindowLoaded()
|
return app.client.waitUntilWindowLoaded()
|
||||||
.getText("#module_5_helloworld .module-content").should.eventually.equal("www.michaelteeuw.nl");
|
.getText("#module_5_helloworld .module-content").should.eventually.equal("www.michaelteeuw.nl");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
var http = require("http");
|
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var auth = require("http-auth");
|
var auth = require("http-auth");
|
||||||
var express = require("express");
|
var express = require("express");
|
||||||
@@ -17,11 +16,11 @@ var basic = auth.basic(
|
|||||||
|
|
||||||
app.use(auth.connect(basic));
|
app.use(auth.connect(basic));
|
||||||
|
|
||||||
// Set directories availables
|
// Set available directories
|
||||||
var directories = ["/tests/configs"];
|
var directories = ["/tests/configs"];
|
||||||
var directory;
|
var directory;
|
||||||
rootPath = path.resolve(__dirname + "/../../");
|
rootPath = path.resolve(__dirname + "/../../");
|
||||||
for (i in directories) {
|
for (var i in directories) {
|
||||||
directory = directories[i];
|
directory = directories[i];
|
||||||
app.use(directory, express.static(path.resolve(rootPath + directory)));
|
app.use(directory, express.static(path.resolve(rootPath + directory)));
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
const chai = require("chai");
|
const expect = require("chai").expect;
|
||||||
const expect = chai.expect;
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const {JSDOM} = require("jsdom");
|
const {JSDOM} = require("jsdom");
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
const chai = require("chai");
|
const expect = require("chai").expect;
|
||||||
const expect = chai.expect;
|
|
||||||
const deprecated = require("../../../js/deprecated");
|
const deprecated = require("../../../js/deprecated");
|
||||||
|
|
||||||
describe("Deprecated", function() {
|
describe("Deprecated", function() {
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
const chai = require("chai");
|
const expect = require("chai").expect;
|
||||||
const expect = chai.expect;
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
|
||||||
const helmet = require("helmet");
|
const helmet = require("helmet");
|
||||||
const {JSDOM} = require("jsdom");
|
const {JSDOM} = require("jsdom");
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
var chai = require("chai");
|
var expect = require("chai").expect;
|
||||||
var expect = chai.expect;
|
|
||||||
var Utils = require("../../../js/utils.js");
|
var Utils = require("../../../js/utils.js");
|
||||||
var colors = require("colors/safe");
|
var colors = require("colors/safe");
|
||||||
|
|
||||||
@@ -38,4 +37,3 @@ describe("Utils", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ global.moment = require("moment");
|
|||||||
describe("Functions into modules/default/calendar/calendar.js", function() {
|
describe("Functions into modules/default/calendar/calendar.js", function() {
|
||||||
|
|
||||||
// Fake for use by calendar.js
|
// Fake for use by calendar.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;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
const chai = require("chai");
|
const expect = require("chai").expect;
|
||||||
const expect = chai.expect;
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const {JSDOM} = require("jsdom");
|
const {JSDOM} = require("jsdom");
|
||||||
|
|
||||||
@@ -29,4 +28,3 @@ describe("Test function cmpVersions in js/module.js", function() {
|
|||||||
expect(cmp("1.1", "1.0")).to.equal(1);
|
expect(cmp("1.1", "1.0")).to.equal(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,13 +1,7 @@
|
|||||||
var fs = require("fs");
|
var expect = require("chai").expect;
|
||||||
var path = require("path");
|
|
||||||
var chai = require("chai");
|
|
||||||
var expect = chai.expect;
|
|
||||||
var vm = require("vm");
|
|
||||||
|
|
||||||
|
|
||||||
describe("Functions module currentweather", function() {
|
describe("Functions module currentweather", function() {
|
||||||
|
|
||||||
|
|
||||||
// Fake for use by currentweather.js
|
// Fake for use by currentweather.js
|
||||||
Module = {};
|
Module = {};
|
||||||
config = {};
|
config = {};
|
||||||
@@ -16,7 +10,6 @@ describe("Functions module currentweather", function() {
|
|||||||
Module.definitions[name] = moduleDefinition;
|
Module.definitions[name] = moduleDefinition;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
before(function(){
|
before(function(){
|
||||||
require("../../../modules/default/currentweather/currentweather.js");
|
require("../../../modules/default/currentweather/currentweather.js");
|
||||||
Module.definitions.currentweather.config = {};
|
Module.definitions.currentweather.config = {};
|
||||||
@@ -39,7 +32,7 @@ describe("Functions module currentweather", function() {
|
|||||||
[2.0 , "2"],
|
[2.0 , "2"],
|
||||||
["2.12" , "2"],
|
["2.12" , "2"],
|
||||||
[10.1 , "10"]
|
[10.1 , "10"]
|
||||||
]
|
];
|
||||||
|
|
||||||
values.forEach(value => {
|
values.forEach(value => {
|
||||||
it(`for ${value[0]} should be return ${value[1]}`, function() {
|
it(`for ${value[0]} should be return ${value[1]}`, function() {
|
||||||
@@ -48,7 +41,6 @@ describe("Functions module currentweather", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("this.config.roundTemp is false", function() {
|
describe("this.config.roundTemp is false", function() {
|
||||||
|
|
||||||
before(function(){
|
before(function(){
|
||||||
@@ -66,7 +58,7 @@ describe("Functions module currentweather", function() {
|
|||||||
["2.12" , "2.1"],
|
["2.12" , "2.1"],
|
||||||
[10.1 , "10.1"],
|
[10.1 , "10.1"],
|
||||||
[10.10 , "10.1"]
|
[10.10 , "10.1"]
|
||||||
]
|
];
|
||||||
|
|
||||||
values.forEach(value => {
|
values.forEach(value => {
|
||||||
it(`for ${value[0]} should be return ${value[1]}`, function() {
|
it(`for ${value[0]} should be return ${value[1]}`, function() {
|
||||||
|
@@ -1,13 +1,8 @@
|
|||||||
var fs = require("fs");
|
var expect = require("chai").expect;
|
||||||
var path = require("path");
|
|
||||||
var chai = require("chai");
|
|
||||||
var expect = chai.expect;
|
|
||||||
var vm = require("vm");
|
|
||||||
|
|
||||||
|
|
||||||
describe("Functions into modules/default/newsfeed/newsfeed.js", function() {
|
describe("Functions into modules/default/newsfeed/newsfeed.js", function() {
|
||||||
|
|
||||||
Module = {}
|
Module = {};
|
||||||
Module.definitions = {};
|
Module.definitions = {};
|
||||||
Module.register = function (name, moduleDefinition) {
|
Module.register = function (name, moduleDefinition) {
|
||||||
Module.definitions[name] = moduleDefinition;
|
Module.definitions[name] = moduleDefinition;
|
||||||
@@ -32,6 +27,5 @@ describe("Functions into modules/default/newsfeed/newsfeed.js", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,9 +1,4 @@
|
|||||||
var fs = require("fs");
|
var expect = require("chai").expect;
|
||||||
var path = require("path");
|
|
||||||
var chai = require("chai");
|
|
||||||
var expect = chai.expect;
|
|
||||||
var vm = require("vm");
|
|
||||||
|
|
||||||
|
|
||||||
describe("Functions module weatherforecast", function() {
|
describe("Functions module weatherforecast", function() {
|
||||||
|
|
||||||
@@ -35,7 +30,7 @@ describe("Functions module weatherforecast", function() {
|
|||||||
[2.0 , "2"],
|
[2.0 , "2"],
|
||||||
["2.12" , "2"],
|
["2.12" , "2"],
|
||||||
[10.1 , "10"]
|
[10.1 , "10"]
|
||||||
]
|
];
|
||||||
|
|
||||||
values.forEach(value => {
|
values.forEach(value => {
|
||||||
it(`for ${value[0]} should be return ${value[1]}`, function() {
|
it(`for ${value[0]} should be return ${value[1]}`, function() {
|
||||||
@@ -44,7 +39,6 @@ describe("Functions module weatherforecast", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("this.config.roundTemp is false", function() {
|
describe("this.config.roundTemp is false", function() {
|
||||||
|
|
||||||
before(function(){
|
before(function(){
|
||||||
@@ -62,7 +56,7 @@ describe("Functions module weatherforecast", function() {
|
|||||||
["2.12" , "2.1"],
|
["2.12" , "2.1"],
|
||||||
[10.1 , "10.1"],
|
[10.1 , "10.1"],
|
||||||
[10.10 , "10.1"]
|
[10.10 , "10.1"]
|
||||||
]
|
];
|
||||||
|
|
||||||
values.forEach(value => {
|
values.forEach(value => {
|
||||||
it(`for ${value[0]} should be return ${value[1]}`, function() {
|
it(`for ${value[0]} should be return ${value[1]}`, function() {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var chai = require("chai");
|
var expect = require("chai").expect;
|
||||||
var expect = chai.expect;
|
|
||||||
var vm = require("vm");
|
var vm = require("vm");
|
||||||
|
|
||||||
before(function() {
|
before(function() {
|
||||||
@@ -62,5 +61,4 @@ describe("Default modules set in modules/default/defaultmodules.js", function()
|
|||||||
expect(fs.existsSync(path.join(this.sandbox.global.root_path, "modules/default", defaultModule))).to.equal(true);
|
expect(fs.existsSync(path.join(this.sandbox.global.root_path, "modules/default", defaultModule))).to.equal(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var chai = require("chai");
|
var expect = require("chai").expect;
|
||||||
var expect = chai.expect;
|
|
||||||
var vm = require("vm");
|
var vm = require("vm");
|
||||||
|
|
||||||
before(function() {
|
before(function() {
|
||||||
@@ -66,6 +65,4 @@ describe("'global.root_path' set in js/app.js", function() {
|
|||||||
versionPackage = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
|
versionPackage = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
|
||||||
expect(this.sandbox.global.version).to.equal(versionPackage);
|
expect(this.sandbox.global.version).to.equal(versionPackage);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
3
translations/en.json
Normal file → Executable file
3
translations/en.json
Normal file → Executable file
@@ -31,5 +31,6 @@
|
|||||||
"UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.",
|
"UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.",
|
||||||
"UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch.",
|
"UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch.",
|
||||||
|
|
||||||
"FEELS": "Feels like"
|
"FEELS": "Feels like",
|
||||||
|
"PRECIP": "PoP"
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,6 @@
|
|||||||
"WNW": "VNV",
|
"WNW": "VNV",
|
||||||
"NW": "NV",
|
"NW": "NV",
|
||||||
"NNW": "NNV",
|
"NNW": "NNV",
|
||||||
"FEELS": "Känns som",
|
|
||||||
|
|
||||||
"UPDATE_NOTIFICATION": "MagicMirror² uppdatering finns tillgänglig.",
|
"UPDATE_NOTIFICATION": "MagicMirror² uppdatering finns tillgänglig.",
|
||||||
"UPDATE_NOTIFICATION_MODULE": "Uppdatering finns tillgänglig av {MODULE_NAME} modulen.",
|
"UPDATE_NOTIFICATION_MODULE": "Uppdatering finns tillgänglig av {MODULE_NAME} modulen.",
|
||||||
|
@@ -45,4 +45,3 @@ var translations = {
|
|||||||
|
|
||||||
if (typeof module !== "undefined") {module.exports = translations;}
|
if (typeof module !== "undefined") {module.exports = translations;}
|
||||||
|
|
||||||
|
|
||||||
|
5
vendor/package-lock.json
generated
vendored
5
vendor/package-lock.json
generated
vendored
@@ -1093,6 +1093,11 @@
|
|||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"suncalc": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.8.0.tgz",
|
||||||
|
"integrity": "sha1-HZiYEJVjB4dQ9JlKlZ5lTYdqy/U="
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
1
vendor/package.json
vendored
Normal file → Executable file
1
vendor/package.json
vendored
Normal file → Executable file
@@ -14,6 +14,7 @@
|
|||||||
"moment": "^2.17.1",
|
"moment": "^2.17.1",
|
||||||
"moment-timezone": "^0.5.11",
|
"moment-timezone": "^0.5.11",
|
||||||
"nunjucks": "^3.0.1",
|
"nunjucks": "^3.0.1",
|
||||||
|
"suncalc": "^1.8.0",
|
||||||
"weathericons": "^2.1.0"
|
"weathericons": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
vendor/vendor.js
vendored
Normal file → Executable file
3
vendor/vendor.js
vendored
Normal file → Executable file
@@ -13,7 +13,8 @@ var vendor = {
|
|||||||
"weather-icons.css": "node_modules/weathericons/css/weather-icons.css",
|
"weather-icons.css": "node_modules/weathericons/css/weather-icons.css",
|
||||||
"weather-icons-wind.css": "node_modules/weathericons/css/weather-icons-wind.css",
|
"weather-icons-wind.css": "node_modules/weathericons/css/weather-icons-wind.css",
|
||||||
"font-awesome.css": "css/font-awesome.css",
|
"font-awesome.css": "css/font-awesome.css",
|
||||||
"nunjucks.js": "node_modules/nunjucks/browser/nunjucks.min.js"
|
"nunjucks.js": "node_modules/nunjucks/browser/nunjucks.min.js",
|
||||||
|
"suncalc.js": "node_modules/suncalc/suncalc.js"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof module !== "undefined"){module.exports = vendor;}
|
if (typeof module !== "undefined"){module.exports = vendor;}
|
||||||
|
Reference in New Issue
Block a user