diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..25422889
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,7 @@
+vendor/
+!/vendor/vendor.js
+/modules/**
+!/modules/default/**
+!/modules/node_helper
+!/modules/node_helper/**
+!/modules/default/defaultmodules.js
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 00000000..83309505
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,14 @@
+{
+ "rules": {
+ "indent": ["error", "tab"],
+ "quotes": ["error", "double"],
+ "max-len": ["error", 250],
+ "curly": "error",
+ "camelcase": ["error", {"properties": "never"}]
+ },
+ "env": {
+ "browser": true,
+ "node": true,
+ "es6": true
+ }
+}
\ No newline at end of file
diff --git a/.jscsrc b/.jscsrc
deleted file mode 100644
index 4638b44f..00000000
--- a/.jscsrc
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "preset": "google",
- "validateIndentation": "\t",
- "validateQuoteMarks": "\"",
- "maximumLineLength": 250,
- "requireCurlyBraces": [],
- "requireCamelCaseOrUpperCaseIdentifiers": false,
- "excludeFiles": [".jscsrc"],
-}
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..c78f0847
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+language: node_js
+node_js:
+ - "6"
+ - "5.1"
+ - "4"
+ - "0.12"
+before_script:
+ - npm install grunt-cli -g
+script: grunt
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 00000000..ac499fe3
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,28 @@
+module.exports = function(grunt) {
+ require("time-grunt")(grunt);
+ grunt.initConfig({
+ pkg: grunt.file.readJSON("package.json"),
+ eslint: {
+ options: {
+ configFile: ".eslintrc.json"
+ },
+ target: ["js/*.js", "modules/default/*.js", "serveronly/*.js", "*.js"]
+ },
+ postcss: {
+ lint: {
+ options: {
+ processors: [
+ require("stylelint")({"extends": "stylelint-config-standard", "font-family-name-quotes": "double-where-recommended"}),
+ require("postcss-reporter")({ clearMessages: true })
+ ]
+ },
+ dist: {
+ src: "**/**/**/**/**/**/**/**.css"
+ }
+ }
+ }
+ });
+ grunt.loadNpmTasks("grunt-eslint");
+ grunt.loadNpmTasks("grunt-postcss");
+ grunt.registerTask("default", ["eslint", "postcss:lint"]);
+};
\ No newline at end of file
diff --git a/README.md b/README.md
index 12dada9a..1c7c1263 100644
--- a/README.md
+++ b/README.md
@@ -104,7 +104,7 @@ The following modules are installed by default.
- [**Hello World**](modules/default/helloworld)
- [**Alert**](modules/default/alert)
-For more available modules, check out out the wiki page: [MagicMirror² Modules](https://github.com/MichMich/MagicMirror/wiki/MagicMirror²-Modules). If you want to build your own modules, check out the [MagicMirror² Module Development Documentation](modules).
+For more available modules, check out out the wiki page: [MagicMirror² Modules](https://github.com/MichMich/MagicMirror/wiki/MagicMirror²-Modules). If you want to build your own modules, check out the [MagicMirror² Module Development Documentation](modules) and don't forget to add it to the wiki and the [forum](https://forum.magicmirror.builders/category/7/showcase)!
## Known issues
@@ -127,3 +127,4 @@ Please keep the following in mind:
- **New Features**: please please discuss in a GitHub issue before you start to alter a big part of the code. Without discussion upfront, the pull request will not be accepted / merged.
Thanks for your help in making MagicMirror² better!
+
diff --git a/index.html b/index.html
index d08291cc..26972810 100644
--- a/index.html
+++ b/index.html
@@ -6,8 +6,8 @@
-
+
diff --git a/installers/raspberry.sh b/installers/raspberry.sh
index 3c1687c0..31803864 100644
--- a/installers/raspberry.sh
+++ b/installers/raspberry.sh
@@ -18,7 +18,7 @@
echo "Installing helper tools ..."
sudo apt-get install curl wget build-essential unzip || exit
ARM=$(uname -m) # Determine which Pi is running.
-NODE_LATEST="v6.0.0" # Set the latest version here.
+NODE_LATEST=$(curl -l http://api.jordidepoortere.com/nodejs-latest/) # Fetch the latest version of Node.js.
DOWNLOAD_URL="https://nodejs.org/dist/latest/node-$NODE_LATEST-linux-$ARM.tar.gz" # Construct the download URL.
echo "Installing Latest Node.js ..."
diff --git a/js/app.js b/js/app.js
index 012d33ad..1ffbafb8 100644
--- a/js/app.js
+++ b/js/app.js
@@ -12,11 +12,11 @@ var path = require("path");
// The next part is here to prevent a major exception when there
// is no internet connection. This could probable be solved better.
-process.on('uncaughtException', function (err) {
- console.log("Whoops! There was an uncaught exception...");
- console.error(err);
- console.log("MagicMirror will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?");
- console.log("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues");
+process.on("uncaughtException", function (err) {
+ console.log("Whoops! There was an uncaught exception...");
+ console.error(err);
+ console.log("MagicMirror will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?");
+ console.log("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues");
});
/* App - The core app.
@@ -41,7 +41,7 @@ var App = function() {
var config = Object.assign(defaults, c);
callback(config);
} catch (e) {
- console.error('WARNING! Could not find config. Please create one.');
+ console.error("WARNING! Could not find config. Please create one.");
callback(defaults);
}
};
@@ -98,7 +98,7 @@ var App = function() {
/* start(callback)
* This methods starts the core app.
* 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 executs the callback with the config as argument.
*
* argument callback function - The callback function.
*/
@@ -130,7 +130,7 @@ var App = function() {
console.log("Sockets connected & modules started ...");
- if (typeof callback === 'function') {
+ if (typeof callback === "function") {
callback(config);
}
diff --git a/js/class.js b/js/class.js
index 69b0cb09..b1793f16 100644
--- a/js/class.js
+++ b/js/class.js
@@ -48,8 +48,9 @@
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
- if (!initializing && this.init)
- this.init.apply(this, arguments);
+ if (!initializing && this.init) {
+ this.init.apply(this, arguments);
+ }
}
// Populate our constructed prototype object
diff --git a/js/defaults.js b/js/defaults.js
index 28cc2613..1d81252c 100644
--- a/js/defaults.js
+++ b/js/defaults.js
@@ -38,6 +38,14 @@ var defaults = {
text: "See README for more information."
}
},
+ {
+ module: "helloworld",
+ position: "middle_center",
+ classes: "xsmall",
+ config: {
+ text: "If you get this message while your config file is already created, your config file probably contains an error. Use a JavaScript linter to validate your file."
+ }
+ },
{
module: "helloworld",
position: "bottom_bar",
diff --git a/js/loader.js b/js/loader.js
index 2dbd40b2..baf5e3ac 100644
--- a/js/loader.js
+++ b/js/loader.js
@@ -1,7 +1,4 @@
/* global config, vendor, MM, Log, Module */
-/* jshint unused:false */
-/* jshint -W061 */
-
/* Magic Mirror
* Module and File loaders.
*
@@ -34,7 +31,15 @@ var Loader = (function() {
loadNextModule();
});
} else {
- startModules();
+ // All modules loaded. Load custom.css
+ // This is done after all the moduels so we can
+ // overwrite all the defined styls.
+
+ loadFile('css/custom.css', function() {
+ // custom.css loaded. Start all modules.
+ startModules();
+ });
+
}
};
@@ -165,31 +170,26 @@ var Loader = (function() {
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
switch (extension.toLowerCase()) {
- case "js":
- Log.log("Load script: " + fileName);
-
- var script = document.createElement("script");
- script.type = "text/javascript";
- script.src = fileName;
- script.onload = function() {
- if (typeof callback === "function") {callback();}
- };
-
- document.getElementsByTagName("body")[0].appendChild(script);
+ case "js":
+ Log.log("Load script: " + fileName);
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.src = fileName;
+ script.onload = function() {
+ if (typeof callback === "function") {callback();}
+ };
+ document.getElementsByTagName("body")[0].appendChild(script);
break;
-
- case "css":
- Log.log("Load stylesheet: " + fileName);
-
- var stylesheet = document.createElement("link");
- stylesheet.rel = "stylesheet";
- stylesheet.type = "text/css";
- stylesheet.href = fileName;
- stylesheet.onload = function() {
- if (typeof callback === "function") {callback();}
- };
-
- document.getElementsByTagName("head")[0].appendChild(stylesheet);
+ case "css":
+ Log.log("Load stylesheet: " + fileName);
+ var stylesheet = document.createElement("link");
+ stylesheet.rel = "stylesheet";
+ stylesheet.type = "text/css";
+ stylesheet.href = fileName;
+ stylesheet.onload = function() {
+ if (typeof callback === "function") {callback();}
+ };
+ document.getElementsByTagName("head")[0].appendChild(stylesheet);
break;
}
diff --git a/js/main.js b/js/main.js
index 7a585425..b159687a 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,5 +1,4 @@
/* global Log, Loader, Module, config, defaults */
-/* jshint -W020 */
/* Magic Mirror
* Main System
@@ -165,7 +164,7 @@ var MM = (function() {
clearTimeout(module.showHideTimer);
module.showHideTimer = setTimeout(function() {
// To not take up any space, we just make the position absolute.
- // since it's fade out anyway, we can see it lay above or
+ // since it"s fade out anyway, we can see it lay above or
// below other modules. This works way better than adjusting
// the .display property.
moduleWrapper.style.position = "absolute";
@@ -434,7 +433,27 @@ var MM = (function() {
})();
// Add polyfill for Object.assign.
-if (typeof Object.assign != 'function') { (function () { Object.assign = function (target) { 'use strict'; if (target === undefined || target === null) { throw new TypeError('Cannot convert undefined or null to object'); } var output = Object(target); for (var index = 1; index < arguments.length; index++) { var source = arguments[index]; if (source !== undefined && source !== null) { for (var nextKey in source) { if (source.hasOwnProperty(nextKey)) { output[nextKey] = source[nextKey]; } } } } return output; }; })(); }
-
+if (typeof Object.assign != "function") {
+ (function() {
+ Object.assign = function(target) {
+ "use strict";
+ if (target === undefined || target === null) {
+ throw new TypeError("Cannot convert undefined or null to object");
+ }
+ var output = Object(target);
+ for (var index = 1; index < arguments.length; index++) {
+ var source = arguments[index];
+ if (source !== undefined && source !== null) {
+ for (var nextKey in source) {
+ if (source.hasOwnProperty(nextKey)) {
+ output[nextKey] = source[nextKey];
+ }
+ }
+ }
+ }
+ return output;
+ };
+ })();
+}
MM.init();
diff --git a/js/module.js b/js/module.js
index f0b0103d..f21fdec6 100644
--- a/js/module.js
+++ b/js/module.js
@@ -111,7 +111,7 @@ var Module = Class.extend({
},
/*********************************************
- * The methods below don't need subclassing. *
+ * The methods below don"t need subclassing. *
*********************************************/
/* setData(data)
@@ -138,7 +138,7 @@ var Module = Class.extend({
},
/* socket()
- * Returns a socket object. If it doesn't exsist, it's created.
+ * Returns a socket object. If it doesn"t exsist, it"s created.
* It also registers the notification callback.
*/
socket: function() {
@@ -223,9 +223,9 @@ var Module = Class.extend({
var translations = this.getTranslations();
var translationFile = translations && (translations[config.language.toLowerCase()] || translations.en) || undefined;
if(translationFile) {
- Translator.load(this, translationFile, callback);
+ Translator.load(this, translationFile, callback);
} else {
- callback();
+ callback();
}
},
@@ -236,7 +236,7 @@ var Module = Class.extend({
* argument defaultValue string - The default value if no translation was found. (Optional)
*/
translate: function(key, defaultValue) {
- return Translator.translate(this, key) || defaultValue || '';
+ return Translator.translate(this, key) || defaultValue || "";
},
/* updateDom(speed)
@@ -299,7 +299,7 @@ Module.create = function(name) {
return obj;
}
- var temp = obj.constructor(); // give temp the original obj's constructor
+ var temp = obj.constructor(); // give temp the original obj"s constructor
for (var key in obj) {
temp[key] = cloneObject(obj[key]);
}
diff --git a/js/translator.js b/js/translator.js
index 87612ed9..b4c2ab53 100644
--- a/js/translator.js
+++ b/js/translator.js
@@ -47,7 +47,7 @@ var Translator = (function() {
_loadJSON: function(file, callback) {
var xhr = new XMLHttpRequest();
xhr.overrideMimeType("application/json");
- xhr.open('GET', file, true);
+ xhr.open("GET", file, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == "200") {
callback(JSON.parse(xhr.responseText));
diff --git a/modules/default/alert/alert.js b/modules/default/alert/alert.js
index bff91504..084161ac 100644
--- a/modules/default/alert/alert.js
+++ b/modules/default/alert/alert.js
@@ -18,7 +18,7 @@ Module.register("alert",{
//Position
position: "center",
//shown at startup
- welcome_message: true,
+ welcome_message: false,
},
getScripts: function() {
return ["classie.js", "modernizr.custom.js", "notificationFx.js"];
diff --git a/modules/default/alert/notificationFx.js b/modules/default/alert/notificationFx.js
index b74c9d14..ca77208d 100644
--- a/modules/default/alert/notificationFx.js
+++ b/modules/default/alert/notificationFx.js
@@ -144,7 +144,10 @@
if (ev.target !== self.ntf) return false;
this.removeEventListener(animEndEventName, onEndAnimationFn);
}
- self.options.wrapper.removeChild(this);
+
+ if (this.parentNode === self.options.wrapper) {
+ self.options.wrapper.removeChild(this);
+ }
};
if (support.animations) {
diff --git a/modules/default/calendar/README.md b/modules/default/calendar/README.md
index cfe4e2b8..00378263 100644
--- a/modules/default/calendar/README.md
+++ b/modules/default/calendar/README.md
@@ -112,6 +112,14 @@ The following properties can be configured:
+
+
displayRepeatingCountTitle
+
Show count title for yearly repeating events (e.g. "X. Birthday", "X. Anniversary")
+
+ Possible values:true or false
+ Default value:false
+
+
@@ -154,5 +162,12 @@ config: {
Possible values: See Font Awsome website.
+
+
repeatingCountTitle
+
The count title for yearly repating events in this calendar.
+ Example:
+ 'Birthday'
+
+
diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js
index 5e315586..2d494499 100644
--- a/modules/default/calendar/calendar.js
+++ b/modules/default/calendar/calendar.js
@@ -15,6 +15,8 @@ Module.register("calendar",{
maximumNumberOfDays: 365,
displaySymbol: true,
defaultSymbol: "calendar", // Fontawsome Symbol see http://fontawesome.io/cheatsheet/
+ displayRepeatingCountTitle: false,
+ defaultRepeatingCountTitle: '',
maxTitleLength: 25,
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
animationSpeed: 2000,
@@ -46,7 +48,8 @@ Module.register("calendar",{
return {
en: "translations/en.json",
de: "translations/de.json",
- nl: "translations/nl.json"
+ nl: "translations/nl.json",
+ fr: "translations/fr.json"
};
},
@@ -113,8 +116,23 @@ Module.register("calendar",{
eventWrapper.appendChild(symbolWrapper);
}
- var titleWrapper = document.createElement("td");
- titleWrapper.innerHTML = this.titleTransform(event.title);
+ var titleWrapper = document.createElement("td"),
+ repeatingCountTitle = '';
+
+
+ if (this.config.displayRepeatingCountTitle) {
+
+ repeatingCountTitle = this.countTitleForUrl(event.url);
+
+ if(repeatingCountTitle !== '') {
+ var thisYear = new Date().getFullYear(),
+ yearDiff = thisYear - event.firstYear;
+
+ repeatingCountTitle = ', '+ yearDiff + '. ' + repeatingCountTitle;
+ }
+ }
+
+ titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle;
titleWrapper.className = "title bright";
eventWrapper.appendChild(titleWrapper);
@@ -239,6 +257,23 @@ Module.register("calendar",{
return this.config.defaultSymbol;
},
+ /* countTitleForUrl(url)
+ * Retrieves the name for a specific url.
+ *
+ * argument url sting - Url to look for.
+ *
+ * return string - The Symbol
+ */
+ countTitleForUrl: function(url) {
+ for (var c in this.config.calendars) {
+ var calendar = this.config.calendars[c];
+ if (calendar.url === url && typeof calendar.repeatingCountTitle === "string") {
+ return calendar.repeatingCountTitle;
+ }
+ }
+
+ return this.config.defaultRepeatingCountTitle;
+ },
/* shorten(string, maxLength)
* Shortens a sting if it's longer than maxLenthg.
diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js
index ac558e2f..00a17fd9 100644
--- a/modules/default/calendar/calendarfetcher.js
+++ b/modules/default/calendar/calendarfetcher.js
@@ -55,14 +55,16 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe
if (event.type === "VEVENT") {
- //console.log(event);
-
var startDate = (event.start.length === 8) ? moment(event.start, "YYYYMMDD") : moment(new Date(event.start));
var endDate;
if (typeof event.end !== "undefined") {
endDate = (event.end.length === 8) ? moment(event.end, "YYYYMMDD") : moment(new Date(event.end));
} else {
- endDate = startDate;
+ if (!isFacebookBirthday) {
+ endDate = startDate;
+ } else {
+ endDate = moment(startDate).add(1, 'days');
+ }
}
// calculate the duration f the event for use with recurring events.
@@ -84,14 +86,15 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe
title: (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary,
startDate: startDate.format("x"),
endDate: endDate.format("x"),
- fullDayEvent: isFullDayEvent(event)
+ fullDayEvent: isFullDayEvent(event),
+ firstYear: event.start.getFullYear()
});
}
}
} else {
// console.log("Single event ...");
// Single event.
- var fullDayEvent = isFullDayEvent(event);
+ var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event);
var title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
if (!fullDayEvent && endDate < new Date()) {
diff --git a/modules/default/calendar/translations/fr.json b/modules/default/calendar/translations/fr.json
new file mode 100644
index 00000000..45f48b8b
--- /dev/null
+++ b/modules/default/calendar/translations/fr.json
@@ -0,0 +1,7 @@
+{
+ "TODAY": "Aujourd'hui"
+ , "TOMORROW": "Demain"
+ , "RUNNING": "Se termine dans"
+ , "LOADING": "Chargement des RDV …"
+ , "EMPTY": "Aucun RDV."
+}
diff --git a/modules/default/calendar/vendor/ical.js/node-ical.js b/modules/default/calendar/vendor/ical.js/node-ical.js
index 294908ea..2f6ef3ef 100644
--- a/modules/default/calendar/vendor/ical.js/node-ical.js
+++ b/modules/default/calendar/vendor/ical.js/node-ical.js
@@ -32,7 +32,7 @@ ical.objectHandlers['END'] = function(val, params, curr, stack){
if (curr.start.length === 8) {
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(curr.start);
if (comps) {
- curr.start = new Date (comps[1], comps[2], comps[3]);
+ curr.start = new Date (comps[1], comps[2] - 1, comps[3]);
}
}
diff --git a/modules/default/currentweather/README.md b/modules/default/currentweather/README.md
index 2ce54aac..3be92edb 100644
--- a/modules/default/currentweather/README.md
+++ b/modules/default/currentweather/README.md
@@ -97,6 +97,13 @@ The following properties can be configured:
Default value:false
+
+
useBeaufort
+
Pick between using the Beaufort scale for wind speed or using the default units.
+ Possible values:true or false
+ Default value:true
+