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 + + lang The language of the days.
diff --git a/modules/default/currentweather/currentweather.js b/modules/default/currentweather/currentweather.js index 4ab04c4f..fb557935 100644 --- a/modules/default/currentweather/currentweather.js +++ b/modules/default/currentweather/currentweather.js @@ -20,6 +20,7 @@ Module.register("currentweather",{ showPeriod: true, showPeriodUpper: false, showWindDirection: false, + useBeaufort: true, lang: config.language, initialLoadDelay: 0, // 0 seconds delay @@ -204,7 +205,14 @@ Module.register("currentweather",{ */ processWeather: function(data) { this.temperature = this.roundValue(data.main.temp); - this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed)); + + if (this.config.useBeaufort){ + this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed)); + }else { + this.windSpeed = parseFloat(data.wind.speed).toFixed(0); + } + + this.windDirection = this.deg2Cardinal(data.wind.deg); this.weatherType = this.config.iconTable[data.weather[0].icon]; diff --git a/modules/default/newsfeed/README.md b/modules/default/newsfeed/README.md index 936e5c2c..47f73b01 100644 --- a/modules/default/newsfeed/README.md +++ b/modules/default/newsfeed/README.md @@ -21,8 +21,7 @@ modules: [ url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", }, { - title: "BBC - ", + title: "BBC", url: "http://feeds.bbci.co.uk/news/video_and_audio/news_front_page/rss.xml?edition=uk", }, ] diff --git a/package.json b/package.json index e815b2f1..d202526a 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,13 @@ "homepage": "https://github.com/MichMich/MagicMirror#readme", "devDependencies": { "electron-prebuilt": "latest", + "grunt": "latest", + "grunt-eslint": "latest", + "grunt-postcss": "latest", + "postcss-reporter": "latest", "stylelint": "latest", - "stylelint-config-standard": "latest" + "stylelint-config-standard": "latest", + "time-grunt": "latest" }, "dependencies": { "express": "latest", @@ -38,7 +43,7 @@ "iconv-lite": "latest", "moment": "latest", "request": "latest", - "snyk": "^1.13.2", + "snyk": "latest", "socket.io": "latest", "valid-url": "latest", "walk": "latest", diff --git a/serveronly/index.js b/serveronly/index.js index 3b8e0f16..ea435a8e 100644 --- a/serveronly/index.js +++ b/serveronly/index.js @@ -1,5 +1,5 @@ -var app = require('../js/app.js'); +var app = require("../js/app.js"); app.start(function(config) { - console.log(''); - console.log('Ready to go! Please point your browser to: http://localhost:' + config.port); + console.log(""); + console.log("Ready to go! Please point your browser to: http://localhost:" + config.port); });