diff --git a/.eslintrc.json b/.eslintrc.json index b5e07288..6936dd7e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,5 +12,11 @@ "browser": true, "node": true, "es6": true - } + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "globalReturn": true + } + } } diff --git a/.travis.yml b/.travis.yml index ec119b78..62cc10a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,8 @@ language: node_js node_js: - - "8" - "7" - - "6" - - "5.1" before_script: + - yarn danger ci - npm install grunt-cli -g - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" diff --git a/CHANGELOG.md b/CHANGELOG.md index ad3cfd9e..1bb4d751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,48 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [2.3.0] - 2018-04-01 + +### Added + +- Add new settings in compliments module: setting time intervals for morning and afternoon +- Add system notification `MODULE_DOM_CREATED` for notifying each module when their Dom has been fully loaded. +- Add types for module. +- Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR. +- Allow to scroll in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures) +- Changed 'compliments.js' - update DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments +- Automated unit tests utils, deprecated, translator, cloneObject(lockstrings) +- Automated integration tests translations +- Add advanced filtering to the excludedEvents configuration of the default calendar module +- New currentweather module config option: `showFeelsLike`: Shows how it actually feels like. (wind chill or heat index) +- New currentweather module config option: `useKMPHwind`: adds an option to see wind speed in Kmph instead of just m/s or Beaufort. +- Add dc:date to parsing in newsfeed module, which allows parsing of more rss feeds. + +### Changed +- Add link to GitHub repository which contains the respective Dockerfile. +- Optimized automated unit tests cloneObject, cmpVersions +- Update notifications use now translation templates instead of normal strings. +- Yarn can be used now as an installation tool +- Changed Electron dependency to v1.7.13. + +### Fixed +- News article in fullscreen (iframe) is now shown in front of modules. +- Forecast respects maxNumberOfDays regardless of endpoint. +- Fix exception on translation of objects. + ## [2.2.2] - 2018-01-02 ### Added - Add missing `package-lock.json`. +### Changed + +- Changed Electron dependency to v1.7.10. + ## [2.2.1] - 2018-01-01 ### Fixed - - Fixed linting errors. ## [2.2.0] - 2018-01-01 diff --git a/README.md b/README.md index 8fa876a6..b795ab97 100644 --- a/README.md +++ b/README.md @@ -16,114 +16,123 @@ MagicMirror² focuses on a modular plugin system and uses [Electron](http://elec ## Table Of Contents -- [Usage](#usage) +- [Installation](#installation) + - [Raspberry Pi](#raspberrypi) + - [General](#general) + - [Server Only](#server-only) + - [Client Only](#client-only) + - [Docker](#docker) - [Configuration](#configuration) - [Modules](#modules) +- [Updating](#updating) - [Known Issues](#known-issues) - [Community](#community) - [Contributing Guidelines](#contributing-guidelines) +- [Manifesto](#manifesto) -## Usage +## Installation -### Raspberry Pi Support -Electron, the app wrapper around MagicMirror², only supports the Raspberry Pi 2 & 3. The Raspberry Pi 1 is currently **not** supported. If you want to run this on a Raspberry Pi 1, use the [server only](#server-only) feature and setup a fullscreen browser yourself. +### Raspberry Pi -### Automatic Installer (Raspberry Pi Only!) +#### Automatic Installation (Raspberry Pi only!) + +*Electron*, the app wrapper around MagicMirror², only supports the Raspberry Pi 2/3. The Raspberry Pi 0/1 is currently **not** supported. If you want to run this on a Raspberry Pi 1, use the [server only](#server-only) feature and setup a fullscreen browser yourself. (Yes, people have managed to run MM² also on a Pi0, so if you insist, search in the forums.) + +Note that you will need to install the lastest full version of Raspbian, **don't use the Lite version**. Execute the following command on your Raspberry Pi to install MagicMirror²: -```` + +```bash bash -c "$(curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/master/installers/raspberry.sh)" -```` +``` -### Manual Installation +#### Manual Installation -1. Download and install the latest Node.js version. +1. Download and install the latest *Node.js* version. 2. Clone the repository and check out the master branch: `git clone https://github.com/MichMich/MagicMirror` -3. Enter the repository: `cd ~/MagicMirror` -4. Install and run the app: `npm install && npm start` +3. Enter the repository: `cd MagicMirror/` +4. Install and run the app with: `npm install && npm start` \ + For **Server Only** use: `npm install && node serveronly` . -**Important:** `npm start` does **not** work via SSH, use `DISPLAY=:0 nohup npm start &` instead. This starts the mirror on the remote display. -**Note:** if you want to debug on Raspberry Pi you can use `npm start dev` which will start the MagicMirror app with Dev Tools enabled. +**:warning: Important!** + +- **The installation step for `npm install` will take a very long time**, often with little or no terminal response! \ + For the RPi3 this is **~10** minutes and for the Rpi2 **~25** minutes. \ + Do not interrupt or you risk getting a :broken_heart: by Raspberry Jam. + + +Also note that: + +- `npm start` does **not** work via SSH. But you can use `DISPLAY=:0 nohup npm start &` instead. \ + This starts the mirror on the remote display. +- If you want to debug on Raspberry Pi you can use `npm start dev` which will start MM with *Dev Tools* enabled. +- To access toolbar menu when in mirror mode, hit `ALT` key. +- To toggle the (web) `Developer Tools` from mirror mode, use `CTRL-SHIFT-I` or `ALT` and select `View`. + ### Server Only + In some cases, you want to start the application without an actual app window. In this case, you can start MagicMirror² in server only mode by manually running `node serveronly` or using Docker. This will start the server, after which you can open the application in your browser of choice. Detailed description below. +**Important:** Make sure that you whitelist the interface/ip (`ipWhitelist`) in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server. You also need to set the local host `address` field to `0.0.0.0` in order for the RPi to listen on all interfaces and not only `localhost` (default). + +```javascript +var config = { + address: "0.0.0.0", // default is "localhost" + port: 8080, // default + ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:172.17.0.1"], // default -- need to add your IP here + ... +}; +``` + + ### Client Only -When you have a server running remotely and want to connect a standalone client to this instance, you can manually run `node clientonly --address 192.168.1.5 --port 8080`. (Specify the ip address and port number of the server) -**Important:** Make sure that you whitelist the interface/ip in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server +This is when you already have a server running remotely and want your RPi to connect as a standalone client to this instance, to show the MM from the server. Then from your RPi, you run it with: `node clientonly --address 192.168.1.5 --port 8080`. (Specify the ip address and port number of the server) -#### Docker + +### Docker MagicMirror² in server only mode can be deployed using [Docker](https://docker.com). After a successful [Docker installation](https://docs.docker.com/engine/installation/) you just need to execute the following command in the shell: ```bash docker run -d \ - --publish 80:8080 \ - --restart always \ - --volume ~/magic_mirror/config:/opt/magic_mirror/config \ - --volume ~/magic_mirror/modules:/opt/magic_mirror/modules \ - --name magic_mirror \ - bastilimbach/docker-magicmirror + --publish 80:8080 \ + --restart always \ + --volume ~/magic_mirror/config:/opt/magic_mirror/config \ + --volume ~/magic_mirror/modules:/opt/magic_mirror/modules \ + --name magic_mirror \ + bastilimbach/docker-magicmirror ``` +To get more information about the available Dockerfile versions and configurations head over to the respective [GitHub repository](https://github.com/bastilimbach/docker-MagicMirror). -| **Volumes** | **Description** | -| --- | --- | -| `/opt/magic_mirror/config` | Mount this volume to insert your own config into the docker container. | -| `/opt/magic_mirror/modules` | Mount this volume to add your own custom modules into the docker container. | - -You may need to add your Docker Host IP to your `ipWhitelist` option. If you have some issues setting up this configuration, check [this forum post](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto). - -```javascript -var config = { - ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:172.17.0.1"] -}; -``` - -If you want to run the server on a raspberry pi, use the `raspberry` tag. (bastilimbach/docker-magicmirror:raspberry) - -#### Manual - -1. Download and install the latest Node.js version. -2. Clone the repository and check out the master branch: `git clone https://github.com/MichMich/MagicMirror` -3. Enter the repository: `cd ~/MagicMirror` -4. Install and run the app: `npm install && node serveronly` - -### Raspberry Configuration & Auto Start. - -The following wiki links are helpful in the configuration of your MagicMirror² operating system: -- [Configuring the Raspberry Pi](https://github.com/MichMich/MagicMirror/wiki/Configuring-the-Raspberry-Pi) -- [Auto Starting MagicMirror](https://github.com/MichMich/MagicMirror/wiki/Auto-Starting-MagicMirror) - -### Updating your MagicMirror² - -If you want to update your MagicMirror² to the latest version, use your terminal to go to your Magic Mirror folder and type the following command: - -```bash -git pull && npm install -``` - -If you changed nothing more than the config or the modules, this should work without any problems. -Type `git status` to see your changes, if there are any, you can reset them with `git reset --hard`. After that, git pull should be possible. ## Configuration -1. Duplicate `config/config.js.sample` to `config/config.js`. **Note:** If you used the installer script. This step is already done for you. -2. Modify your required settings. +### Raspberry Specific + +The following wiki links are helpful for the initial configuration of your MagicMirror² operating system: +- [Configuring the Raspberry Pi](https://github.com/MichMich/MagicMirror/wiki/Configuring-the-Raspberry-Pi) +- [Auto Starting MagicMirror](https://github.com/MichMich/MagicMirror/wiki/Auto-Starting-MagicMirror) + + +### General + +1. Copy `config/config.js.sample` to `config/config.js`. \ + **Note:** If you used the installer script. This step is already done for you. + +2. Modify your required settings. \ + Note: You'll can check your configuration running `npm run config:check`. -Note: You'll can check your configuration running the follow command: -```bash -npm run config:check -``` The following properties can be configured: | **Option** | **Description** | | --- | --- | | `port` | The port on which the MagicMirror² server will run on. The default value is `8080`. | -| `address` | The ip address the accept connections. The default open bind `localhost`. Example config: `192.168.10.100`. | -| `ipWhitelist` | The list of IPs from which you are allowed to access the MagicMirror². The default value is `["127.0.0.1", "::ffff:127.0.0.1", "::1"]`. It is possible to specify IPs with subnet masks (`["127.0.0.1", "127.0.0.1/24"]`) or define ip ranges (`["127.0.0.1", ["192.168.0.1", "192.168.0.100"]]`). Set `[]` to allow all IP addresses. For more information about how configure this directive see the [follow post ipWhitelist HowTo](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto) | +| `address` | The *interface* ip address on which to accept connections. The default is `localhost`, which would prevent exposing the built-in webserver to machines on the local network. To expose it to other machines, use: `0.0.0.0`. | +| `ipWhitelist` | The list of IPs from which you are allowed to access the MagicMirror². The default value is `["127.0.0.1", "::ffff:127.0.0.1", "::1"]`, which is from `localhost` only. Add your IP when needed. You can also specify IP ranges with subnet masks (`["127.0.0.1", "127.0.0.1/24"]`) or directly with (`["127.0.0.1", ["192.168.0.1", "192.168.0.100"]]`). Set `[]` to allow all IP addresses. For more information see: [follow post ipWhitelist HowTo](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto) | | `zoom` | This allows to scale the mirror contents with a given zoom factor. The default value is `1.0`| | `language` | The language of the interface. (Note: Not all elements will be localized.) Possible values are `en`, `nl`, `ru`, `fr`, etc., but the default value is `en`. | | `timeFormat` | The form of time notation that will be used. Possible values are `12` or `24`. The default is `24`. | @@ -156,12 +165,20 @@ 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) and don't forget to add it to the wiki and the [forum](https://forum.magicmirror.builders/category/7/showcase)! +For more available modules, check out out the wiki page [MagicMirror² 3rd Party Modules](https://github.com/MichMich/MagicMirror/wiki/3rd-party-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 -- Electron seems to have some issues on certain Raspberry Pi 2's. See [#145](https://github.com/MichMich/MagicMirror/issues/145). -- MagicMirror² (Electron) sometimes quits without an error after an extended period of use. See [#150](https://github.com/MichMich/MagicMirror/issues/150). +## Updating + +If you want to update your MagicMirror² to the latest version, use your terminal to go to your Magic Mirror folder and type the following command: + +```bash +git pull && npm install +``` + +If you changed nothing more than the config or the modules, this should work without any problems. +Type `git status` to see your changes, if there are any, you can reset them with `git reset --hard`. After that, git pull should be possible. + ## Community @@ -180,6 +197,22 @@ Please keep the following in mind: Thanks for your help in making MagicMirror² better! +## Manifesto + +A real Manifesto is still to be written. Till then, Michael's response on [one of the repository issues](https://github.com/MichMich/MagicMirror/issues/1174) gives a great summary: + +> "... I started this project as an ultimate starter project for Raspberry Pi enthusiasts. As a matter of fact, for most of the contributors, the MagicMirror project is the first open source project they ever contributed to. This is one of the reasons why the MagicMirror project is featured in several RasPi magazines. +> +>The project has a lot of opportunities for improvement. We could use a powerful framework like Vue to ramp up the development speed. We could use SASS for better/easier css implementations. We could make it an NPM installable package. And as you say, we could bundle it up. The big downside of of of these changes is that it over complicates things: a user no longer will be able to open just one file and make a small modification and see how it works out. +> +>Of course, a bundled version can be complimentary to the regular un-bundled version. And I'm sure a lot of (new) users will opt for the bundled version. But this means those users won't be motivated to take a peek under the hood. They will just remain 'users'. They won't become contributors, and worse: they won't be motivated to take their first steps in software development. +> +>And to be honest: motivating curious users to step out of their comfort zone and take those first steps is what drives me in this project. Therefor my ultimate goal is this project is to keep it as accessible as possible." +> +> ~ Michael Teeuw + + +


MagPi Top 50 diff --git a/dangerfile.js b/dangerfile.js new file mode 100644 index 00000000..1cbe69db --- /dev/null +++ b/dangerfile.js @@ -0,0 +1,17 @@ +import { danger, fail, warn } from "danger" + +// 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 +if (!danger.git.modified_files.includes("CHANGELOG.md")) { + warn("Please include an updated `CHANGELOG.md` file.
This way we can keep track of all the contributions.") +} + +// Check if the PR request is send to the master branch. +// This should only be done by MichMich. +if (danger.github.pr.base.ref === "master" && danger.github.pr.user.login !== "MichMich") { + // Check if the PR body or title includes the text: #accepted. + // If not, the PR will fail. + if ((danger.github.pr.body + danger.github.pr.title).includes("#accepted")) { + fail("Please send all your pull requests to the `develop` branch.
Pull requests on the `master` branch will not be accepted.") + } +} \ No newline at end of file diff --git a/installers/raspberry.sh b/installers/raspberry.sh index ecdcc842..4e9c20d8 100644 --- a/installers/raspberry.sh +++ b/installers/raspberry.sh @@ -42,7 +42,7 @@ sudo apt-get update || echo -e "\e[91mUpdate failed, carrying on installation .. # Installing helper tools echo -e "\e[96mInstalling helper tools ...\e[90m" -sudo apt-get install curl wget git build-essential unzip || exit +sudo apt-get --assume-yes install curl wget git build-essential unzip || exit # Check if we need to install or upgrade Node.js. echo -e "\e[96mCheck current Node installation ...\e[0m" @@ -82,7 +82,7 @@ if $NODE_INSTALL; then # The NODE_STABLE_BRANCH variable will need to be manually adjusted when a new branch is released. (e.g. 7.x) # Only tested (stable) versions are recommended as newer versions could break MagicMirror. - NODE_STABLE_BRANCH="6.x" + NODE_STABLE_BRANCH="9.x" curl -sL https://deb.nodesource.com/setup_$NODE_STABLE_BRANCH | sudo -E bash - sudo apt-get install -y nodejs echo -e "\e[92mNode.js installation Done!\e[0m" @@ -101,7 +101,7 @@ if [ -d "$HOME/MagicMirror" ] ; then fi echo -e "\e[96mCloning MagicMirror ...\e[90m" -if git clone https://github.com/MichMich/MagicMirror.git; then +if git clone --depth=1 https://github.com/MichMich/MagicMirror.git; then echo -e "\e[92mCloning MagicMirror Done!\e[0m" else echo -e "\e[91mUnable to clone MagicMirror." @@ -149,7 +149,7 @@ else fi # Use pm2 control like a service MagicMirror -read -p "Do you want use pm2 for auto starting of your MagicMirror (y/n)?" choice +read -p "Do you want use pm2 for auto starting of your MagicMirror (y/N)?" choice if [[ $choice =~ ^[Yy]$ ]]; then sudo npm install -g pm2 sudo su -c "env PATH=$PATH:/usr/bin pm2 startup linux -u pi --hp /home/pi" diff --git a/js/class.js b/js/class.js index ec75f6f2..ceccc6f1 100644 --- a/js/class.js +++ b/js/class.js @@ -92,7 +92,4 @@ function cloneObject(obj) { /*************** DO NOT EDIT THE LINE BELOW ***************/ if (typeof module !== "undefined") { module.exports = Class; - module.exports._test = { - cloneObject: cloneObject - } } diff --git a/js/main.js b/js/main.js index f499da62..9ee3f789 100644 --- a/js/main.js +++ b/js/main.js @@ -1,5 +1,5 @@ /* global Log, Loader, Module, config, defaults */ -/* jshint -W020 */ +/* jshint -W020, esversion: 6 */ /* Magic Mirror * Main System @@ -19,42 +19,49 @@ var MM = (function() { * are configured for a specific position. */ var createDomObjects = function() { - for (var m in modules) { - var module = modules[m]; + var domCreationPromises = []; - if (typeof module.data.position === "string") { - - var wrapper = selectWrapper(module.data.position); - - var dom = document.createElement("div"); - dom.id = module.identifier; - dom.className = module.name; - - if (typeof module.data.classes === "string") { - dom.className = "module " + dom.className + " " + module.data.classes; - } - - dom.opacity = 0; - wrapper.appendChild(dom); - - if (typeof module.data.header !== "undefined" && module.data.header !== "") { - var moduleHeader = document.createElement("header"); - moduleHeader.innerHTML = module.data.header; - moduleHeader.className = "module-header"; - dom.appendChild(moduleHeader); - } - - var moduleContent = document.createElement("div"); - moduleContent.className = "module-content"; - dom.appendChild(moduleContent); - - updateDom(module, 0); + modules.forEach(module => { + if (typeof module.data.position !== "string") { + return; } - } + + var wrapper = selectWrapper(module.data.position); + + var dom = document.createElement("div"); + dom.id = module.identifier; + dom.className = module.name; + + if (typeof module.data.classes === "string") { + dom.className = "module " + dom.className + " " + module.data.classes; + } + + dom.opacity = 0; + wrapper.appendChild(dom); + + if (typeof module.data.header !== "undefined" && module.data.header !== "") { + var moduleHeader = document.createElement("header"); + moduleHeader.innerHTML = module.data.header; + moduleHeader.className = "module-header"; + dom.appendChild(moduleHeader); + } + + var moduleContent = document.createElement("div"); + moduleContent.className = "module-content"; + dom.appendChild(moduleContent); + + var domCreationPromise = updateDom(module, 0); + domCreationPromises.push(domCreationPromise); + domCreationPromise.then(() => { + sendNotification("MODULE_DOM_CREATED", null, null, module); + }).catch(Log.error); + }); updateWrapperStates(); - sendNotification("DOM_OBJECTS_CREATED"); + Promise.all(domCreationPromises).then(() => { + sendNotification("DOM_OBJECTS_CREATED"); + }); }; /* selectWrapper(position) @@ -79,11 +86,12 @@ var MM = (function() { * argument notification string - The identifier of the notification. * argument payload mixed - The payload of the notification. * argument sender Module - The module that sent the notification. + * argument sendTo Module - The module to send the notification to. (optional) */ - var sendNotification = function(notification, payload, sender) { + var sendNotification = function(notification, payload, sender, sendTo) { for (var m in modules) { var module = modules[m]; - if (module !== sender) { + if (module !== sender && (!sendTo || module === sendTo)) { module.notificationReceived(notification, payload, sender); } } @@ -94,19 +102,53 @@ var MM = (function() { * * argument module Module - The module that needs an update. * argument speed Number - The number of microseconds for the animation. (optional) + * + * return Promise - Resolved when the dom is fully updated. */ var updateDom = function(module, speed) { - var newContent = module.getDom(); - var newHeader = module.getHeader(); + return new Promise((resolve) => { + var newContentPromise = module.getDom(); + var newHeader = module.getHeader(); - if (!module.hidden) { + if (!(newContentPromise instanceof Promise)) { + // convert to a promise if not already one to avoid if/else's everywhere + newContentPromise = Promise.resolve(newContentPromise); + } + + newContentPromise.then((newContent) => { + var updatePromise = updateDomWithContent(module, speed, newHeader, newContent); + + updatePromise.then(resolve).catch(Log.error); + }).catch(Log.error); + }); + }; + + /* updateDomWithContent(module, speed, newHeader, newContent) + * Update the dom with the specified content + * + * argument module Module - The module that needs an update. + * argument speed Number - The number of microseconds for the animation. (optional) + * argument newHeader String - The new header that is generated. + * argument newContent Domobject - The new content that is generated. + * + * return Promise - Resolved when the module dom has been updated. + */ + var updateDomWithContent = function(module, speed, newHeader, newContent) { + return new Promise((resolve) => { + if (module.hidden || !speed) { + updateModuleContent(module, newHeader, newContent); + resolve(); + return; + } if (!moduleNeedsUpdate(module, newHeader, newContent)) { + resolve(); return; } if (!speed) { updateModuleContent(module, newHeader, newContent); + resolve(); return; } @@ -115,16 +157,16 @@ var MM = (function() { if (!module.hidden) { showModule(module, speed / 2); } + resolve(); }); - } else { - updateModuleContent(module, newHeader, newContent); - } + }); }; /* moduleNeedsUpdate(module, newContent) * Check if the content has changed. * * argument module Module - The module to check. + * argument newHeader String - The new header that is generated. * argument newContent Domobject - The new content that is generated. * * return bool - Does the module need an update? @@ -152,6 +194,7 @@ var MM = (function() { * Update the content of a module on screen. * * argument module Module - The module to check. + * argument newHeader String - The new header that is generated. * argument newContent Domobject - The new content that is generated. */ var updateModuleContent = function(module, newHeader, newContent) { diff --git a/js/module.js b/js/module.js index 464509ee..8eb6dca9 100644 --- a/js/module.js +++ b/js/module.js @@ -78,34 +78,33 @@ var Module = Class.extend({ * This method can to be subclassed if the module wants to display info on the mirror. * Alternatively, the getTemplete method could be subclassed. * - * return domobject - The dom to display. + * return DomObject | Promise - The dom or a promise with the dom to display. */ getDom: function () { - var div = document.createElement("div"); - var template = this.getTemplate(); - var templateData = this.getTemplateData(); + return new Promise((resolve) => { + var div = document.createElement("div"); + var template = this.getTemplate(); + var templateData = this.getTemplateData(); - // Check to see if we need to render a template string or a file. - if (/^.*((\.html)|(\.njk))$/.test(template)) { - // the template is a filename - this.nunjucksEnvironment().render(template, templateData, function (err, res) { - if (err) { - Log.error(err) - } + // Check to see if we need to render a template string or a file. + if (/^.*((\.html)|(\.njk))$/.test(template)) { + // the template is a filename + this.nunjucksEnvironment().render(template, templateData, function (err, res) { + if (err) { + Log.error(err) + } - // The inner content of the div will be set after the template is received. - // This isn't the most optimal way, but since it's near instant - // it probably won't be an issue. - // If it gives problems, we can always add a way to pre fetch the templates. - // Let's not over optimise this ... KISS! :) - div.innerHTML = res; - }); - } else { - // the template is a template string. - div.innerHTML = this.nunjucksEnvironment().renderString(template, templateData); - } + div.innerHTML = res; - return div; + resolve(div); + }); + } else { + // the template is a template string. + div.innerHTML = this.nunjucksEnvironment().renderString(template, templateData); + + resolve(div); + } + }); }, /* getHeader() @@ -477,11 +476,3 @@ Module.register = function (name, moduleDefinition) { Log.log("Module registered: " + name); Module.definitions[name] = moduleDefinition; }; - -if (typeof exports != "undefined") { // For testing purpose only - // A good a idea move the function cmpversions a helper file. - // It's used into other side. - exports._test = { - cmpVersions: cmpVersions - } -} diff --git a/js/translator.js b/js/translator.js index b82a739e..fe9d0ec0 100644 --- a/js/translator.js +++ b/js/translator.js @@ -126,6 +126,9 @@ var Translator = (function() { // variables: {timeToWait: "2 hours", work: "painting"} // to: "Please wait for 2 hours before continuing with painting." function createStringFromTemplate(template, variables) { + if(Object.prototype.toString.call(template) !== "[object String]") { + return template; + } if(variables.fallback && !template.match(new RegExp("\{.+\}"))) { template = variables.fallback; } @@ -156,11 +159,12 @@ var Translator = (function() { return key; }, - /* load(module, file, callback) + /* load(module, file, isFallback, callback) * Load a translation file (json) and remember the data. * * argument module Module - The module to load the translation file for. * argument file string - Path of the file we want to load. + * argument isFallback boolean - Flag to indicate fallback translations. * argument callback function - Function called when done. */ load: function(module, file, isFallback, callback) { @@ -216,10 +220,12 @@ var Translator = (function() { // defined translation after the following line. for (var first in translations) {break;} - Log.log("Loading core translation fallback file: " + translations[first]); - loadJSON(translations[first], function(translations) { - self.coreTranslationsFallback = translations; - }); + if (first) { + Log.log("Loading core translation fallback file: " + translations[first]); + loadJSON(translations[first], function(translations) { + self.coreTranslationsFallback = translations; + }); + } }, }; })(); diff --git a/module-types.ts b/module-types.ts new file mode 100644 index 00000000..3b8825eb --- /dev/null +++ b/module-types.ts @@ -0,0 +1,31 @@ +type ModuleProperties = { + defaults?: object, + start?(): void, + getHeader?(): string, + getTemplate?(): string, + getTemplateData?(): object, + notificationReceived?(notification: string, payload: any, sender: object): void, + socketNotificationReceived?(notification: string, payload: any): void, + suspend?(): void, + resume?(): void, + getDom?(): HTMLElement, + getStyles?(): string[], + [key: string]: any, +}; + +export declare const Module: { + register(moduleName: string, moduleProperties: ModuleProperties): void; +}; + +export declare const Log: { + info(message?: any, ...optionalParams: any[]): void, + log(message?: any, ...optionalParams: any[]): void, + error(message?: any, ...optionalParams: any[]): void, + warn(message?: any, ...optionalParams: any[]): void, + group(groupTitle?: string, ...optionalParams: any[]): void, + groupCollapsed(groupTitle?: string, ...optionalParams: any[]): void, + groupEnd(): void, + time(timerName?: string): void, + timeEnd(timerName?: string): void, + timeStamp(timerName?: string): void, +}; \ No newline at end of file diff --git a/modules/README.md b/modules/README.md index 76973996..8bc8f86e 100644 --- a/modules/README.md +++ b/modules/README.md @@ -2,6 +2,42 @@ This document describes the way to develop your own MagicMirror² modules. +Table of Contents: + +- Module structure + - Files + +- The Core module file: modulename.js + - Available module instance properties + - Subclassable module methods + - Module instance methods + - Visibility locking + +- The Node Helper: node_helper.js + - Available module instance properties + - Subclassable module methods + - Module instance methods + +- MagicMirror Helper Methods + - Module Selection + +- MagicMirror Logger + +--- + + +## General Advice + +As MagicMirror has gained huge popularity, so has the number of available modules. For new users and developers alike, it is very time consuming to navigate around the various repositories in order to find out what exactly a certain modules does, how it looks and what it depends on. Unfortunately, this information is rarely available, nor easily obtained without having to install it first. +Therefore **we highly recommend you to include the following information in your README file.** + +- A high quality screenshot of your working module +- A short, one sentence, clear description what it does (duh!) +- What external API's it depend on, including web links to those +- Wheteher the API/request require a key and the user limitations of those. (Is it free?) + +Surely this also help you get better recognition and feedback for your work. + ## Module structure All modules are loaded in the `modules` folder. The default modules are grouped together in the `modules/default` folder. Your module should be placed in a subfolder of `modules`. Note that any file or folder your create in the `modules` folder will be ignored by git, allowing you to upgrade the MagicMirror² without the loss of your files. @@ -14,7 +50,7 @@ A module can be placed in one single folder. Or multiple modules can be grouped - **modulename/public** - Any files in this folder can be accesed via the browser on `/modulename/filename.ext`. - **modulename/anyfileorfolder** Any other file or folder in the module folder can be used by the core module script. For example: *modulename/css/modulename.css* would be a good path for your additional module styles. -## Core module file: modulename.js +## The Core module file: modulename.js This is the script in which the module will be defined. This script is required in order for the module to be used. In it's most simple form, the core module file must contain: ````javascript Module.register("modulename",{}); @@ -44,30 +80,16 @@ As you can see, the `Module.register()` method takes two arguments: the name of ### Available module instance properties After the module is initialized, the module instance has a few available module properties: -#### `this.name` -**String** +| Instance Property | Type | Description | +|:----------------- |:---- |:----------- | +| `this.name` | String | The name of the module. | +| `this.identifier` | String | This is a unique identifier for the module instance. | +| `this.hidden` | Boolean | This represents if the module is currently hidden (faded away). | +| `this.config` | Boolean | The configuration of the module instance as set in the user's `config.js` file. This config will also contain the module's defaults if these properties are not over-written by the user config. | +| `this.data` | Object | The data object contain additional metadata about the module instance. (See below) | -The name of the module. -#### `this.identifier` -**String** - -This is a unique identifier for the module instance. - -#### `this.hidden` -**Boolean** - -This represents if the module is currently hidden (faded away). - -#### `this.config` -**Boolean** - -The configuration of the module instance as set in the user's config.js file. This config will also contain the module's defaults if these properties are not over written by the user config. - -#### `this.data` -**Object** - -The data object contains additional metadata about the module instance: +The `this.data` data object contain the follwoing metadata: - `data.classes` - The classes which are added to the module dom wrapper. - `data.file` - The filename of the core module file. - `data.path` - The path of the module folder. @@ -230,11 +252,12 @@ notificationReceived: function(notification, payload, sender) { } ```` -**Note:** the system sends two notifications when starting up. These notifications could come in handy! +**Note:** the system sends three notifications when starting up. These notifications could come in handy! - `ALL_MODULES_STARTED` - All modules are started. You can now send notifications to other modules. - `DOM_OBJECTS_CREATED` - All dom objects are created. The system is now ready to perform visual changes. +- `MODULE_DOM_CREATED` - This module's dom has been fully loaded. You can now access your module's dom objects. #### `socketNotificationReceived: function(notification, payload)` diff --git a/modules/default/alert/README.md b/modules/default/alert/README.md index 3abf2fcd..c57ba793 100644 --- a/modules/default/alert/README.md +++ b/modules/default/alert/README.md @@ -60,5 +60,5 @@ self.sendNotification("SHOW_ALERT", {}); | `timer` (optional) | How long the alert should stay visible in ms.
**Important:** If you do not use the `timer`, it is your duty to hide the alert by using `self.sendNotification("HIDE_ALERT");`!

**Possible values:** `int` `float`
**Default value:** `none` ## Open Source Licenses -###[NotificationStyles](https://github.com/codrops/NotificationStyles) +### [NotificationStyles](https://github.com/codrops/NotificationStyles) See [ympanus.net](http://tympanus.net/codrops/licensing/) for license. diff --git a/modules/default/calendar/README.md b/modules/default/calendar/README.md index 8ce9608b..2481b77b 100644 --- a/modules/default/calendar/README.md +++ b/modules/default/calendar/README.md @@ -46,7 +46,7 @@ The following properties can be configured: | `urgency` | When using a timeFormat of `absolute`, the `urgency` setting allows you to display events within a specific time frame as `relative`. This allows events within a certain time frame to be displayed as relative (in xx days) while others are displayed as absolute dates

**Possible values:** a positive integer representing the number of days for which you want a relative date, for example `7` (for 7 days)

**Default value:** `7` | `broadcastEvents` | If this property is set to true, the calendar will broadcast all the events to all other modules with the notification message: `CALENDAR_EVENTS`. The event objects are stored in an array and contain the following fields: `title`, `startDate`, `endDate`, `fullDayEvent`, `location` and `geo`.

**Possible values:** `true`, `false`

**Default value:** `true` | `hidePrivate` | Hides private calendar events.

**Possible values:** `true` or `false`
**Default value:** `false` -| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown.

**Example:** `['Birthday', 'Hide This Event']`
**Default value:** `[]` +| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown.

Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.
**Required**
`filterBy` - string used to determine if filter is applied.
**Optional**
`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]
`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity

**Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}]`
**Default value:** `[]` ### Calendar configuration diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index 12495f78..911eaba4 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -113,11 +113,38 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri title = event.description; } - var excluded = false; + var excluded = false, + dateFilter = null; + for (var f in excludedEvents) { - var filter = excludedEvents[f]; - if (title.toLowerCase().includes(filter.toLowerCase())) { - excluded = true; + var filter = excludedEvents[f], + testTitle = title.toLowerCase(), + until = null; + + if (filter instanceof Object) { + if (typeof filter.until !== "undefined") { + until = filter.until; + } + + // If additional advanced filtering is added in, this section + // must remain last as we overwrite the filter object with the + // filterBy string + if (filter.caseSensitive) { + filter = filter.filterBy; + testTitle = title; + } else { + filter = filter.filterBy.toLowerCase(); + } + } else { + filter = filter.toLowerCase(); + } + + if (testTitle.includes(filter)) { + if (until) { + dateFilter = until; + } else { + excluded = true; + } break; } } @@ -137,6 +164,11 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri for (var d in dates) { startDate = moment(new Date(dates[d])); endDate = moment(parseInt(startDate.format("x")) + duration, "x"); + + if (timeFilterApplies(now, endDate, dateFilter)) { + continue; + } + if (endDate.format("x") > now) { newEvents.push({ title: title, @@ -171,6 +203,10 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri continue; } + if (timeFilterApplies(now, endDate, dateFilter)) { + continue; + } + // Every thing is good. Add it to the list. newEvents.push({ @@ -236,6 +272,28 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri return false; }; + /* timeFilterApplies() + * Determines if the user defined time filter should apply + * + * argument now Date - Date object using previously created object for consistency + * argument endDate Moment - Moment object representing the event end date + * argument filter string - The time to subtract from the end date to determine if an event should be shown + * + * return bool - The event should be filtered out + */ + var timeFilterApplies = function(now, endDate, filter) { + if (filter) { + var until = filter.split(" "), + value = parseInt(until[0]), + increment = until[1].slice("-1") === "s" ? until[1] : until[1] + "s", // Massage the data for moment js + filterUntil = moment(endDate.format()).subtract(value, increment); + + return now < filterUntil.format("x"); + } + + return false; + }; + /* public methods */ /* startFetch() diff --git a/modules/default/calendar/vendor/ical.js/test/test.js b/modules/default/calendar/vendor/ical.js/test/test.js index 2aeff1ea..041a4a13 100644 --- a/modules/default/calendar/vendor/ical.js/test/test.js +++ b/modules/default/calendar/vendor/ical.js/test/test.js @@ -108,7 +108,7 @@ vows.describe('node-ical').addBatch({ assert.equal(topic.end.getFullYear(), 1998); assert.equal(topic.end.getUTCMonth(), 2); assert.equal(topic.end.getUTCDate(), 15); - assert.equal(topic.end.getUTCHours(), 00); + assert.equal(topic.end.getUTCHours(), 0); assert.equal(topic.end.getUTCMinutes(), 30); } } @@ -146,7 +146,7 @@ vows.describe('node-ical').addBatch({ } , 'has a start datetime' : function(topic) { assert.equal(topic.start.getFullYear(), 2011); - assert.equal(topic.start.getMonth(), 09); + assert.equal(topic.start.getMonth(), 9); assert.equal(topic.start.getDate(), 11); } @@ -192,7 +192,7 @@ vows.describe('node-ical').addBatch({ } , 'has a start' : function(topic){ assert.equal(topic.start.tz, 'America/Phoenix') - assert.equal(topic.start.toISOString(), new Date(2011, 10, 09, 19, 0,0).toISOString()) + assert.equal(topic.start.toISOString(), new Date(2011, 10, 9, 19, 0,0).toISOString()) } } } @@ -208,7 +208,7 @@ vows.describe('node-ical').addBatch({ })[0]; } , 'has a start' : function(topic){ - assert.equal(topic.start.toISOString(), new Date(2011, 07, 04, 12, 0,0).toISOString()) + assert.equal(topic.start.toISOString(), new Date(2011, 7, 4, 12, 0,0).toISOString()) } } , 'event with rrule' :{ @@ -249,7 +249,7 @@ vows.describe('node-ical').addBatch({ }, 'task completed': function(task){ assert.equal(task.completion, 100); - assert.equal(task.completed.toISOString(), new Date(2013, 06, 16, 10, 57, 45).toISOString()); + assert.equal(task.completed.toISOString(), new Date(2013, 6, 16, 10, 57, 45).toISOString()); } } } @@ -367,7 +367,7 @@ vows.describe('node-ical').addBatch({ assert.equal(topic.end.getFullYear(), 2014); assert.equal(topic.end.getMonth(), 3); assert.equal(topic.end.getUTCHours(), 19); - assert.equal(topic.end.getUTCMinutes(), 00); + assert.equal(topic.end.getUTCMinutes(), 0); } } }, diff --git a/modules/default/compliments/README.md b/modules/default/compliments/README.md index 10a45b8f..2b381b39 100644 --- a/modules/default/compliments/README.md +++ b/modules/default/compliments/README.md @@ -32,6 +32,12 @@ The following properties can be configured: | `compliments` | The list of compliments.

**Possible values:** An object with four arrays: `morning`, `afternoon`, `evening` and `anytime`. See _compliment configuration_ below.
**Default value:** See _compliment configuration_ below. | `remoteFile` | External file from which to load the compliments

**Possible values:** Path to a JSON file containing compliments, configured as per the value of the _compliments configuration_ (see below). An object with four arrays: `morning`, `afternoon`, `evening` and `anytime`. - `compliments.json`
**Default value:** `null` (Do not load from file) | `classes` | Override the CSS classes of the div showing the compliments

**Default value:** `thin xlarge bright` +| `morningStartTime` | Time in hours (in 24 format), after which the mode of "morning" will begin
**Possible values:** `0` - `24`

**Default value:** `3` +| `morningEndTime` | Time in hours (in 24 format), after which the mode of "morning" will end
**Possible values:** `0` - `24`

**Default value:** `12` +| `afternoonStartTime` | Time in hours (in 24 format), after which the mode "afternoon" will begin
**Possible values:** `0` - `24`

**Default value:** `12` +| `afternoonEndTime` | Time in hours (in 24 format), after which the mode "afternoon" will end
**Possible values:** `0` - `24`

**Default value:** `17` + +All the rest of the time that does not fall into the morningStartTime-morningEndTime and afternoonStartTime-afternoonEndTime ranges is considered "evening". ### Compliment configuration diff --git a/modules/default/compliments/compliments.js b/modules/default/compliments/compliments.js index a0af458d..ef77128e 100644 --- a/modules/default/compliments/compliments.js +++ b/modules/default/compliments/compliments.js @@ -32,7 +32,11 @@ Module.register("compliments", { }, updateInterval: 30000, remoteFile: null, - fadeSpeed: 4000 + fadeSpeed: 4000, + morningStartTime: 3, + morningEndTime: 12, + afternoonStartTime: 12, + afternoonEndTime: 17 }, // Set currentweather from module @@ -49,14 +53,15 @@ Module.register("compliments", { this.lastComplimentIndex = -1; + var self = this; if (this.config.remoteFile != null) { this.complimentFile((response) => { this.config.compliments = JSON.parse(response); + self.updateDom(); }); } // Schedule update timer. - var self = this; setInterval(function() { self.updateDom(self.config.fadeSpeed); }, this.config.updateInterval); @@ -98,9 +103,9 @@ Module.register("compliments", { var hour = moment().hour(); var compliments; - if (hour >= 3 && hour < 12 && this.config.compliments.hasOwnProperty("morning")) { + if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) { compliments = this.config.compliments.morning.slice(0); - } else if (hour >= 12 && hour < 17 && this.config.compliments.hasOwnProperty("afternoon")) { + } else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) { compliments = this.config.compliments.afternoon.slice(0); } else if(this.config.compliments.hasOwnProperty("evening")) { compliments = this.config.compliments.evening.slice(0); diff --git a/modules/default/currentweather/README.md b/modules/default/currentweather/README.md index 8eb633c0..030b04bf 100644 --- a/modules/default/currentweather/README.md +++ b/modules/default/currentweather/README.md @@ -43,7 +43,9 @@ The following properties can be configured: | `showWindDirectionAsArrow` | Show the wind direction as an arrow instead of abbreviation

**Possible values:** `true` or `false`
**Default value:** `false` | `showHumidity` | Show the current humidity

**Possible values:** `true` or `false`
**Default value:** `false` | `showIndoorTemperature` | If you have another module that emits the INDOOR_TEMPERATURE notification, the indoor temperature will be displayed
**Default value:** `false` -| `onlyTemp` | Show only current Temperature and weather icon.

**Possible values:** `true` or `false`
**Default value:** `false` +| `onlyTemp` | Show only current Temperature and weather icon without windspeed, sunset, sunrise time and feels like.

**Possible values:** `true` or `false`
**Default value:** `false` +| `showFeelsLike` | Shows the Feels like temperature weather.

**Possible values:**`true` or `false`
**Default value:** `true` +| `useKMPHWind` | Uses KMPH as units for windspeed.

**Possible values:**`true` or `false`
**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.

**Possible values:** `en`, `nl`, `ru`, etc ...
**Default value:** uses value of _config.language_ | `decimalSymbol` | The decimal symbol to use.

**Possible values:** `.`, `,` or any other symbol.
**Default value:** `.` diff --git a/modules/default/currentweather/currentweather.js b/modules/default/currentweather/currentweather.js index 8cd3a292..442e8632 100644 --- a/modules/default/currentweather/currentweather.js +++ b/modules/default/currentweather/currentweather.js @@ -23,12 +23,14 @@ Module.register("currentweather",{ showWindDirection: true, showWindDirectionAsArrow: false, useBeaufort: true, + useKMPHwind: false, lang: config.language, decimalSymbol: ".", showHumidity: false, degreeLabel: false, showIndoorTemperature: false, showIndoorHumidity: false, + showFeelsLike: true, initialLoadDelay: 0, // 0 seconds delay retryDelay: 2500, @@ -105,7 +107,7 @@ Module.register("currentweather",{ this.indoorTemperature = null; this.indoorHumidity = null; this.weatherType = null; - + this.feelsLike = null; this.loaded = false; this.scheduleUpdate(this.config.initialLoadDelay); @@ -242,6 +244,19 @@ Module.register("currentweather",{ } wrapper.appendChild(large); + + if (this.config.showFeelsLike && this.config.onlyTemp === false){ + var small = document.createElement("div"); + small.className = "normal medium"; + + var feelsLike = document.createElement("span"); + feelsLike.className = "dimmed"; + feelsLike.innerHTML = "Feels " + this.feelsLike + "°" + degreeLabel; + small.appendChild(feelsLike); + + wrapper.appendChild(small); + } + return wrapper; }, @@ -365,13 +380,71 @@ Module.register("currentweather",{ this.humidity = parseFloat(data.main.humidity); this.temperature = this.roundValue(data.main.temp); + this.feelsLike = 0; if (this.config.useBeaufort){ this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed)); + } else if (this.config.useKMPHwind) { + this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0); } else { this.windSpeed = parseFloat(data.wind.speed).toFixed(0); } + // ONLY WORKS IF TEMP IN C // + var windInMph = parseFloat(data.wind.speed * 2.23694); + + var tempInF = 0; + switch (this.config.units){ + case "metric": tempInF = 1.8 * this.temperature + 32; + break; + case "imperial": tempInF = this.temperature; + break; + case "default": + var tc = this.temperature - 273.15; + tempInF = 1.8 * tc + 32; + break; + } + + if (windInMph > 3 && tempInF < 50){ + // windchill + var windchillinF = Math.round(35.74+0.6215*tempInF-35.75*Math.pow(windInMph,0.16)+0.4275*tempInF*Math.pow(windInMph,0.16)); + var windChillInC = (windchillinF - 32) * (5/9); + // this.feelsLike = windChillInC.toFixed(0); + + switch (this.config.units){ + case "metric": this.feelsLike = windChillInC.toFixed(0); + break; + case "imperial": this.feelsLike = windChillInF.toFixed(0); + break; + case "default": + var tc = windChillInC - 273.15; + this.feelsLike = tc.toFixed(0); + break; + } + + } else if (tempInF > 80 && this.humidity > 40){ + // heat index + var Hindex = -42.379 + 2.04901523*tempInF + 10.14333127*this.humidity + - 0.22475541*tempInF*this.humidity - 6.83783*Math.pow(10,-3)*tempInF*tempInF + - 5.481717*Math.pow(10,-2)*this.humidity*this.humidity + + 1.22874*Math.pow(10,-3)*tempInF*tempInF*this.humidity + + 8.5282*Math.pow(10,-4)*tempInF*this.humidity*this.humidity + - 1.99*Math.pow(10,-6)*tempInF*tempInF*this.humidity*this.humidity; + + switch (this.config.units){ + case "metric": this.feelsLike = Hindex.toFixed(0); + break; + case "imperial": this.feelsLike = parseFloat(Hindex * 1.8 + 32).toFixed(0); + break; + case "default": + var tc = Hindex - 273.15; + this.feelsLike = tc.toFixed(0); + break; + } + } else { + this.feelsLike = parseFloat(this.temperature).toFixed(0); + } + this.windDirection = this.deg2Cardinal(data.wind.deg); this.windDeg = data.wind.deg; this.weatherType = this.config.iconTable[data.weather[0].icon]; @@ -497,4 +570,5 @@ Module.register("currentweather",{ var decimals = this.config.roundTemp ? 0 : 1; return parseFloat(temperature).toFixed(decimals); } + }); diff --git a/modules/default/newsfeed/README.md b/modules/default/newsfeed/README.md index a240b60f..d85ea3e9 100644 --- a/modules/default/newsfeed/README.md +++ b/modules/default/newsfeed/README.md @@ -39,7 +39,7 @@ MagicMirror's [notification mechanism](https://github.com/MichMich/MagicMirror/t | ----------------------- | ----------- | `ARTICLE_NEXT` | Shows the next 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.
The module expects that the module's configuration option `showDescription` is set to `false` (default value).

When received a _second consecutive time_, shows the full news article in an IFRAME.
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`. +| `ARTICLE_MORE_DETAILS` | When received the _first time_, shows the corresponding description of the currently displayed news title.
The module expects that the module's configuration option `showDescription` is set to `false` (default value).

When received a _second consecutive time_, shows the full news article in an IFRAME.
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`.

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. Note the payload of the sent notification event is ignored. @@ -72,13 +72,14 @@ The following properties can be configured: | `updateInterval` | How often do you want to display a new headline? (Milliseconds)

**Possible values:**`1000` - `60000`
**Default value:** `10000` (10 seconds) | `animationSpeed` | Speed of the update animation. (Milliseconds)

**Possible values:**`0` - `5000`
**Default value:** `2500` (2.5 seconds) | `maxNewsItems` | Total amount of news items to cycle through. (0 for unlimited)

**Possible values:**`0` - `...`
**Default value:** `0` -| `ignoreOldItems` | Ignore news items that are outdated.

**Possible values:**`true` or `false
**Default value:** `false` +| `ignoreOldItems` | Ignore news items that are outdated.

**Possible values:**`true` or `false`
**Default value:** `false` | `ignoreOlderThan` | How old should news items be before they are considered outdated? (Milliseconds)

**Possible values:**`1` - `...`
**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.

**Possible values:**`'title'`, `'description'`, `'both'` | `startTags` | List the tags you would like to have removed at the beginning of the feed item

**Possible values:** `['TAG']` or `['TAG1','TAG2',...]` | `removeEndTags` | Remove specified tags from the **end** of an item's description and/or title.

**Possible values:**`'title'`, `'description'`, `'both'` | `endTags` | List the tags you would like to have removed at the end of the feed item

**Possible values:** `['TAG']` or `['TAG1','TAG2',...]` | `prohibitedWords` | Remove news feed item if one of these words is found anywhere in the title (case insensitive and greedy matching)

**Possible values:** `['word']` or `['word1','word2',...]` +| `scrollLength` | Scrolls the full news article page by a given number of pixels when a `ARTICLE_MORE_DETAILS` notification is received and the full news article is already displayed.

**Possible values:** `1` or `10000`
**Default value:** `500` The `feeds` property contains an array with multiple objects. These objects have the following properties: diff --git a/modules/default/newsfeed/fetcher.js b/modules/default/newsfeed/fetcher.js index 0cc73e36..a3bf1fa0 100644 --- a/modules/default/newsfeed/fetcher.js +++ b/modules/default/newsfeed/fetcher.js @@ -45,7 +45,7 @@ var Fetcher = function(url, reloadInterval, encoding) { var title = item.title; var description = item.description || item.summary || item.content || ""; - var pubdate = item.pubdate || item.published || item.updated; + var pubdate = item.pubdate || item.published || item.updated || item["dc:date"]; var url = item.url || item.link || ""; if (title && pubdate) { diff --git a/modules/default/newsfeed/newsfeed.js b/modules/default/newsfeed/newsfeed.js index 95f05d60..7b4d7e56 100644 --- a/modules/default/newsfeed/newsfeed.js +++ b/modules/default/newsfeed/newsfeed.js @@ -36,7 +36,8 @@ Module.register("newsfeed",{ removeEndTags: "", startTags: [], endTags: [], - prohibitedWords: [] + prohibitedWords: [], + scrollLength: 500 }, // Define required scripts. @@ -62,6 +63,7 @@ Module.register("newsfeed",{ this.newsItems = []; this.loaded = false; this.activeItem = 0; + this.scrollPosition = 0; this.registerFeeds(); @@ -171,7 +173,6 @@ Module.register("newsfeed",{ var description = document.createElement("div"); description.className = "small light" + (!this.config.wrapDescription ? " no-wrap" : ""); var txtDesc = this.newsItems[this.activeItem].description; - //Log.info('txtDesc.length = ' + txtDesc.length + " - " + this.config.lengthDescription); description.innerHTML = (this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc); wrapper.appendChild(description); } @@ -180,12 +181,14 @@ Module.register("newsfeed",{ var fullArticle = document.createElement("iframe"); fullArticle.className = ""; fullArticle.style.width = "100%"; + // very large height value to allow scrolling + fullArticle.height = "10000"; + fullArticle.style.height = "10000"; fullArticle.style.top = "0"; fullArticle.style.left = "0"; - fullArticle.style.position = "fixed"; - fullArticle.height = window.innerHeight; fullArticle.style.border = "none"; - fullArticle.src = this.newsItems[this.activeItem].url; + fullArticle.src = typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href; + fullArticle.style.zIndex = 1; wrapper.appendChild(fullArticle); } @@ -322,6 +325,10 @@ Module.register("newsfeed",{ resetDescrOrFullArticleAndTimer: function() { this.config.showDescription = false; this.config.showFullArticle = false; + this.scrollPosition = 0; + // reset bottom bar alignment + document.getElementsByClassName("region bottom bar")[0].style.bottom = "0"; + document.getElementsByClassName("region bottom bar")[0].style.top = "inherit"; if(!timer){ this.scheduleUpdateInterval(); } @@ -350,12 +357,27 @@ Module.register("newsfeed",{ } // if "more details" is received the first time: show article summary, on second time show full article else if(notification == "ARTICLE_MORE_DETAILS"){ - this.config.showDescription = !this.config.showDescription; - this.config.showFullArticle = !this.config.showDescription; - clearInterval(timer); - timer = null; - Log.info(this.name + " - showing " + this.config.showDescription ? "article description" : "full article"); - this.updateDom(100); + // full article is already showing, so scrolling down + if(this.config.showFullArticle == true){ + this.scrollPosition += this.config.scrollLength; + window.scrollTo(0, this.scrollPosition); + Log.info(this.name + " - scrolling down"); + Log.info(this.name + " - ARTICLE_MORE_DETAILS, scroll position: " + this.config.scrollLength); + } + // display full article + else { + this.config.showDescription = !this.config.showDescription; + this.config.showFullArticle = !this.config.showDescription; + // make bottom bar align to top to allow scrolling + if(this.config.showFullArticle == true){ + document.getElementsByClassName("region bottom bar")[0].style.bottom = "inherit"; + document.getElementsByClassName("region bottom bar")[0].style.top = "-90px"; + } + clearInterval(timer); + timer = null; + Log.info(this.name + " - showing " + this.config.showDescription ? "article description" : "full article"); + this.updateDom(100); + } } else if(notification == "ARTICLE_LESS_DETAILS"){ this.resetDescrOrFullArticleAndTimer(); Log.info(this.name + " - showing only article titles again"); diff --git a/modules/default/updatenotification/updatenotification.js b/modules/default/updatenotification/updatenotification.js index 306ac6ae..b26517e5 100644 --- a/modules/default/updatenotification/updatenotification.js +++ b/modules/default/updatenotification/updatenotification.js @@ -58,16 +58,19 @@ Module.register("updatenotification", { icon.innerHTML = " "; message.appendChild(icon); - var subtextHtml = this.translate("UPDATE_INFO") - .replace("COMMIT_COUNT", this.status.behind + " " + ((this.status.behind == 1) ? "commit" : "commits")) - .replace("BRANCH_NAME", this.status.current); + var subtextHtml = this.translate("UPDATE_INFO", { + COMMIT_COUNT: this.status.behind + " " + ((this.status.behind == 1) ? "commit" : "commits"), + BRANCH_NAME: this.status.current + }); var text = document.createElement("span"); if (this.status.module == "default") { text.innerHTML = this.translate("UPDATE_NOTIFICATION"); subtextHtml = this.diffLink(subtextHtml); } else { - text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE").replace("MODULE_NAME", this.status.module); + text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", { + MODULE_NAME: this.status.module + }); } message.appendChild(text); diff --git a/modules/default/weatherforecast/weatherforecast.js b/modules/default/weatherforecast/weatherforecast.js index 22322bf3..a95347d9 100644 --- a/modules/default/weatherforecast/weatherforecast.js +++ b/modules/default/weatherforecast/weatherforecast.js @@ -259,7 +259,6 @@ Module.register("weatherforecast",{ if (self.config.forecastEndpoint == "forecast/daily") { self.config.forecastEndpoint = "forecast"; - self.config.maxNumberOfDays = self.config.maxNumberOfDays * 8; Log.warn(self.name + ": Your AppID does not support long term forecasts. Switching to fallback endpoint."); } @@ -298,12 +297,6 @@ Module.register("weatherforecast",{ params += "&units=" + this.config.units; params += "&lang=" + this.config.lang; - /* - * Submit a specific number of days to forecast, between 1 to 16 days. - * The OpenWeatherMap API properly handles values outside of the 1 - 16 range and returns 7 days by default. - * This is simply being pedantic and doing it ourselves. - */ - params += "&cnt=" + (((this.config.maxNumberOfDays < 1) || (this.config.maxNumberOfDays > 16)) ? 7 * 8 : this.config.maxNumberOfDays); params += "&APPID=" + this.config.appid; return params; @@ -354,6 +347,11 @@ Module.register("weatherforecast",{ this.forecast.push(forecastData); lastDay = day; + + // Stop processing when maxNumberOfDays is reached + if (this.forecast.length === this.config.maxNumberOfDays) { + break; + } } else { //Log.log("Compare max: ", forecast.temp.max, parseFloat(forecastData.maxTemp)); forecastData.maxTemp = forecast.temp.max > parseFloat(forecastData.maxTemp) ? this.roundValue(forecast.temp.max) : forecastData.maxTemp; diff --git a/package-lock.json b/package-lock.json index 0a68929a..90eacd8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,57 @@ { "name": "magicmirror", - "version": "2.2.2", + "version": "2.3.0-dev", "lockfileVersion": 1, "requires": true, "dependencies": { + "@octokit/rest": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-14.0.5.tgz", + "integrity": "sha512-zD/udLRqM3VobJSRfwyrFgNuWW2IbdSFE1A9SYG3rJXNt2qZtCfD9NcHVYJvimEkuerG8dnreMZ5a2VGmLQEBw==", + "dev": true, + "requires": { + "before-after-hook": "1.1.0", + "debug": "3.1.0", + "dotenv": "4.0.0", + "https-proxy-agent": "2.1.1", + "is-stream": "1.1.0", + "lodash": "4.17.4", + "proxy-from-env": "1.0.0", + "url-template": "2.0.8" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + } + } + }, + "@types/node": { + "version": "7.0.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.57.tgz", + "integrity": "sha512-Iikf0IAus1OX++3Jrc1R2bsZggO+m22G5ee56JccYKejx5GNT3nHhY8v6J4OXId1hXXlb0n45hcaVwZwQcZZ6w==" + }, "JSV": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=", "dev": true }, - "abbrev": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", - "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", "dev": true }, "accepts": { @@ -26,11 +64,20 @@ } }, "acorn": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", - "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", "dev": true }, + "acorn-globals": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz", + "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", + "dev": true, + "requires": { + "acorn": "5.4.1" + } + }, "acorn-jsx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", @@ -53,6 +100,15 @@ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, + "agent-base": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "dev": true, + "requires": { + "es6-promisify": "5.0.0" + } + }, "ajv": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", @@ -76,9 +132,9 @@ "dev": true }, "ansi-escapes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", - "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", "dev": true }, "ansi-regex": { @@ -247,6 +303,12 @@ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -312,10 +374,10 @@ "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", "dev": true }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, "asynckit": { @@ -365,6 +427,37 @@ "js-tokens": "3.0.2" } }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "core-js": "2.5.1", + "regenerator-runtime": "0.10.5" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.1", + "regenerator-runtime": "0.11.1" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } + } + } + } + }, "babel-runtime": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", @@ -416,6 +509,12 @@ "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=", "dev": true }, + "before-after-hook": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.1.0.tgz", + "integrity": "sha512-VOMDtYPwLbIncTxNoSzRyvaMxtXmLWLUqr8k5AfC1BzLk34HvBXaQX8snOwQZ4c0aX8aSERqtJSiI9/m2u5kuA==", + "dev": true + }, "better-assert": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", @@ -552,6 +651,12 @@ "repeat-element": "1.1.2" } }, + "browser-process-hrtime": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", + "integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=", + "dev": true + }, "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", @@ -723,11 +828,6 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, - "clarinet": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/clarinet/-/clarinet-0.11.0.tgz", - "integrity": "sha1-bMkSuTE43IZ/wnPNNOqQ6D4FRxk=" - }, "cli": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", @@ -773,12 +873,6 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "coffee-script": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz", - "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=", - "dev": true - }, "collapse-white-space": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.3.tgz", @@ -946,6 +1040,12 @@ "dashify": "0.2.2" } }, + "content-type-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz", + "integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ==", + "dev": true + }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -1062,6 +1162,21 @@ "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", "dev": true }, + "cssom": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", + "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", + "dev": true + }, + "cssstyle": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "dev": true, + "requires": { + "cssom": "0.3.2" + } + }, "current-week-number": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/current-week-number/-/current-week-number-1.0.7.tgz", @@ -1076,6 +1191,99 @@ "array-find-index": "1.0.2" } }, + "danger": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/danger/-/danger-3.1.3.tgz", + "integrity": "sha512-0xFaVum2pdPu8W+2ky2+Gxb9AwoA/xNcF6RsogHWtxx4HmfEuV+DjxcWdfyL7OM0GvGL+tXGtntH26rhFzg8+w==", + "dev": true, + "requires": { + "@octokit/rest": "14.0.5", + "babel-polyfill": "6.26.0", + "chalk": "2.3.0", + "commander": "2.13.0", + "debug": "3.1.0", + "get-stdin": "5.0.1", + "hyperlinker": "1.0.0", + "jsome": "2.3.26", + "json5": "0.5.1", + "jsonpointer": "4.0.1", + "lodash.find": "4.6.0", + "lodash.includes": "4.3.0", + "lodash.isobject": "3.0.2", + "lodash.keys": "4.2.0", + "node-cleanup": "2.1.2", + "node-fetch": "1.7.3", + "parse-diff": "0.4.0", + "parse-git-config": "1.1.1", + "parse-github-url": "1.0.2", + "parse-link-header": "1.0.1", + "pinpoint": "1.1.0", + "readline-sync": "1.4.7", + "require-from-string": "2.0.1", + "rfc6902": "2.2.2", + "supports-hyperlinks": "1.0.1", + "vm2": "github:patriksimek/vm2#7e82f90ac705fc44fad044147cb0df09b4c79a57", + "voca": "1.4.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true + }, + "lodash.keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz", + "integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1116,16 +1324,6 @@ "time-zone": "0.1.0" } }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" - } - }, "debug": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", @@ -1240,21 +1438,12 @@ "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=" }, "doctrine": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", - "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } + "esutils": "2.0.2" } }, "dom-serializer": { @@ -1281,6 +1470,15 @@ "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", "dev": true }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "4.0.2" + } + }, "domhandler": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", @@ -1314,6 +1512,12 @@ "is-obj": "1.0.1" } }, + "dotenv": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", + "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=", + "dev": true + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -1335,10 +1539,11 @@ "dev": true }, "electron": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/electron/-/electron-1.4.15.tgz", - "integrity": "sha1-6syv4/Va3gKnRrcGrBS0PbbHzPg=", + "version": "1.7.13", + "resolved": "https://registry.npmjs.org/electron/-/electron-1.7.13.tgz", + "integrity": "sha1-EIUbrsd9aG2VgS80QlwX5IrBQT8=", "requires": { + "@types/node": "7.0.57", "electron-download": "3.3.0", "extract-zip": "1.6.5" } @@ -1370,6 +1575,15 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "0.4.19" + } + }, "end-of-stream": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", @@ -1456,9 +1670,18 @@ } }, "es6-promise": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", - "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==" + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.2.tgz", + "integrity": "sha512-LSas5vsuA6Q4nEdf9wokY5/AJYXry98i0IzXsv49rYsgDGDNDPbqAYR1Pe23iFxygfbGZNR/5VrHXBCh2BhvUQ==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "4.2.2" + } }, "escape-html": { "version": "1.0.3", @@ -1470,34 +1693,62 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "eslint": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.6.1.tgz", - "integrity": "sha1-3cf8f9cL+TIFsLNEm7FqHp59SVA=", + "escodegen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz", + "integrity": "sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==", "dev": true, "requires": { - "ajv": "5.2.2", + "esprima": "3.1.3", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.5.7" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "optional": true + } + } + }, + "eslint": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.16.0.tgz", + "integrity": "sha512-YVXV4bDhNoHHcv0qzU4Meof7/P26B4EuaktMi5L1Tnt52Aov85KmYA8c5D+xyZr/BkhvwUqr011jDSD/QTULxg==", + "dev": true, + "requires": { + "ajv": "5.5.2", "babel-code-frame": "6.26.0", - "chalk": "2.1.0", + "chalk": "2.3.0", "concat-stream": "1.6.0", "cross-spawn": "5.1.0", - "debug": "2.6.8", - "doctrine": "2.0.0", + "debug": "3.1.0", + "doctrine": "2.1.0", "eslint-scope": "3.7.1", - "espree": "3.5.0", + "eslint-visitor-keys": "1.0.0", + "espree": "3.5.2", "esquery": "1.0.0", - "estraverse": "4.2.0", "esutils": "2.0.2", "file-entry-cache": "2.0.0", "functional-red-black-tree": "1.0.1", "glob": "7.1.2", - "globals": "9.18.0", + "globals": "11.1.0", "ignore": "3.3.5", "imurmurhash": "0.1.4", - "inquirer": "3.2.3", - "is-resolvable": "1.0.0", - "js-yaml": "3.9.1", - "json-stable-stringify": "1.0.1", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.10.0", + "json-stable-stringify-without-jsonify": "1.0.1", "levn": "0.3.0", "lodash": "4.17.4", "minimatch": "3.0.4", @@ -1505,7 +1756,7 @@ "natural-compare": "1.4.0", "optionator": "0.8.2", "path-is-inside": "1.0.2", - "pluralize": "4.0.0", + "pluralize": "7.0.0", "progress": "2.0.0", "require-uncached": "1.0.3", "semver": "5.4.1", @@ -1516,15 +1767,15 @@ }, "dependencies": { "ajv": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.2.tgz", - "integrity": "sha1-R8aNaehvXZUxA7AHSpQw3GPaXjk=", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { "co": "4.6.0", "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "ansi-regex": { @@ -1543,20 +1794,20 @@ } }, "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "supports-color": "4.5.0" } }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -1568,10 +1819,16 @@ "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", "dev": true }, + "globals": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.1.0.tgz", + "integrity": "sha512-uEuWt9mqTlPDwSqi+sHjD4nWU/1N+q0fiWI9T1mZpD2UENqX20CFD5T/ziLZvztPaBKl7ZylUi1q6Qfm7E2CiQ==", + "dev": true + }, "js-yaml": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", - "integrity": "sha512-CbcG379L1e+mWBnLvHWWeLs8GyV/EMw862uLI3c+GxVyDHWZcjZinwuBd3iW2pgxgIlksW/1vNJa4to+RvDOww==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", "dev": true, "requires": { "argparse": "1.0.9", @@ -1609,9 +1866,9 @@ } }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", "dev": true, "requires": { "has-flag": "2.0.0" @@ -1629,22 +1886,30 @@ "estraverse": "4.2.0" } }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, "espree": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.0.tgz", - "integrity": "sha1-mDWGJb3QVYYeon4oZ+pyn69GPY0=", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz", + "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", "dev": true, "requires": { - "acorn": "5.1.2", + "acorn": "5.3.0", "acorn-jsx": "3.0.1" + }, + "dependencies": { + "acorn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "dev": true + } } }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, "esquery": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", @@ -1676,12 +1941,6 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", - "dev": true - }, "execall": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execall/-/execall-1.0.0.tgz", @@ -1891,6 +2150,15 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, "external-editor": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", @@ -1966,23 +2234,6 @@ "pend": "1.2.0" } }, - "feedme": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/feedme/-/feedme-1.1.0.tgz", - "integrity": "sha512-3CjvKqgtbK29TH5aQCJuqez96u1TsTtsLaIlLWIT7AUy6lLPKbSsldMAIz7kiBwXrn/8OX8gg4o+hUOpaMRQSQ==", - "requires": { - "clarinet": "0.11.0", - "eventyoshi": "0.2.0", - "sax": "1.2.4" - }, - "dependencies": { - "eventyoshi": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eventyoshi/-/eventyoshi-0.2.0.tgz", - "integrity": "sha1-TsePNW+6NNabhQnrWYtXvCXvi4Y=" - } - } - }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -2030,30 +2281,6 @@ "pinkie-promise": "2.0.1" } }, - "findup-sync": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", - "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", - "dev": true, - "requires": { - "glob": "5.0.15" - }, - "dependencies": { - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } - } - }, "flat-cache": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", @@ -2107,6 +2334,12 @@ "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.0.0.tgz", "integrity": "sha1-e8rUae57lukdEs6zlZx4I1qScuk=" }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", + "dev": true + }, "fs-extra": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", @@ -2139,6 +2372,12 @@ "globule": "1.2.0" } }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -2150,12 +2389,6 @@ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true - }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -2171,6 +2404,17 @@ } } }, + "git-config-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-1.0.1.tgz", + "integrity": "sha1-bTP37WPbDQ4RgTFQO6s6ykfVRmQ=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "fs-exists-sync": "0.1.0", + "homedir-polyfill": "1.0.1" + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -2203,12 +2447,6 @@ "is-glob": "2.0.1" } }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, "globby": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", @@ -2280,64 +2518,6 @@ "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" }, - "grunt": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz", - "integrity": "sha1-6HeHZOlEsY8yuw8QuQeEdcnftWs=", - "dev": true, - "requires": { - "coffee-script": "1.10.0", - "dateformat": "1.0.12", - "eventemitter2": "0.4.14", - "exit": "0.1.2", - "findup-sync": "0.3.0", - "glob": "7.0.6", - "grunt-cli": "1.2.0", - "grunt-known-options": "1.1.0", - "grunt-legacy-log": "1.0.0", - "grunt-legacy-util": "1.0.0", - "iconv-lite": "0.4.19", - "js-yaml": "3.5.5", - "minimatch": "3.0.4", - "nopt": "3.0.6", - "path-is-absolute": "1.0.1", - "rimraf": "2.2.8" - }, - "dependencies": { - "glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "grunt-cli": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", - "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", - "dev": true, - "requires": { - "findup-sync": "0.3.0", - "grunt-known-options": "1.1.0", - "nopt": "3.0.6", - "resolve": "1.1.7" - } - }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - } - } - }, "grunt-eslint": { "version": "20.1.0", "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-20.1.0.tgz", @@ -2345,7 +2525,7 @@ "dev": true, "requires": { "chalk": "2.3.0", - "eslint": "4.6.1" + "eslint": "4.16.0" }, "dependencies": { "ansi-styles": { @@ -2389,66 +2569,6 @@ "strip-json-comments": "2.0.1" } }, - "grunt-known-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", - "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=", - "dev": true - }, - "grunt-legacy-log": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-1.0.0.tgz", - "integrity": "sha1-+4bxgJhHvAfcR4Q/ns1srLYt8tU=", - "dev": true, - "requires": { - "colors": "1.1.2", - "grunt-legacy-log-utils": "1.0.0", - "hooker": "0.2.3", - "lodash": "3.10.1", - "underscore.string": "3.2.3" - } - }, - "grunt-legacy-log-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-1.0.0.tgz", - "integrity": "sha1-p7ji0Ps1taUPSvmG/BEnSevJbz0=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "lodash": "4.3.0" - }, - "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", - "dev": true - } - } - }, - "grunt-legacy-util": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.0.0.tgz", - "integrity": "sha1-OGqnjcbtUJhsKxiVcmWxtIq7m4Y=", - "dev": true, - "requires": { - "async": "1.5.2", - "exit": "0.1.2", - "getobject": "0.1.0", - "hooker": "0.2.3", - "lodash": "4.3.0", - "underscore.string": "3.2.3", - "which": "1.2.14" - }, - "dependencies": { - "lodash": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", - "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", - "dev": true - } - } - }, "grunt-markdownlint": { "version": "1.0.43", "resolved": "https://registry.npmjs.org/grunt-markdownlint/-/grunt-markdownlint-1.0.43.tgz", @@ -2658,6 +2778,15 @@ "resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.5.tgz", "integrity": "sha1-eIspgVsS1Tus9XVkhHbm+QQdEz8=" }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "1.0.0" + } + }, "hooker": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", @@ -2679,6 +2808,15 @@ "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.1.0.tgz", "integrity": "sha512-zXhh/DqgrTXJ7erTN6Fh5k/xjMhDGXCqdYN3wvxUvGUQvnxcFfUd8E+6vLg/nk3ss1TYMb+DhRl25fYABioTvA==" }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "1.0.3" + } + }, "html-tags": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", @@ -2740,6 +2878,33 @@ "sshpk": "1.13.1" } }, + "https-proxy-agent": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.1.1.tgz", + "integrity": "sha512-LK6tQUR/VOkTI6ygAfWUKKP95I+e6M1h7N3PncGu1CATHCnex+CAv9ttR0lbHu1Uk2PXm/WoAHFo6JCGwMjVMw==", + "dev": true, + "requires": { + "agent-base": "4.2.0", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "hyperlinker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz", + "integrity": "sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==", + "dev": true + }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -2801,13 +2966,13 @@ "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" }, "inquirer": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.2.3.tgz", - "integrity": "sha512-Bc3KbimpDTOeQdDj18Ir/rlsGuhBSSNqdOnxaAuKhpkdnMMuKsEGbZD2v5KFF9oso2OU+BPh7+/u5obmFDRmWw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", "dev": true, "requires": { - "ansi-escapes": "2.0.0", - "chalk": "2.1.0", + "ansi-escapes": "3.0.0", + "chalk": "2.3.0", "cli-cursor": "2.1.0", "cli-width": "2.2.0", "external-editor": "2.0.4", @@ -2838,14 +3003,14 @@ } }, "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "supports-color": "4.4.0" + "supports-color": "4.5.0" } }, "is-fullwidth-code-point": { @@ -2880,9 +3045,9 @@ } }, "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", "dev": true, "requires": { "has-flag": "2.0.0" @@ -2890,6 +3055,12 @@ } } }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -3081,13 +3252,16 @@ "dev": true }, "is-resolvable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", - "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true, - "requires": { - "tryit": "1.0.3" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-supported-regexp-flag": { "version": "1.0.0", @@ -3162,16 +3336,6 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, - "js-yaml": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.5.tgz", - "integrity": "sha1-A3fDgBfKvHMisNH7zSWkkWQfL74=", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "2.7.3" - } - }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -3184,6 +3348,53 @@ "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", "dev": true }, + "jsdom": { + "version": "11.6.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.6.2.tgz", + "integrity": "sha512-pAeZhpbSlUp5yQcS6cBQJwkbzmv4tWFaYxHbFVSxzXefqjvtRA851Z5N2P+TguVG9YeUDcgb8pdeVQRJh0XR3Q==", + "dev": true, + "requires": { + "abab": "1.0.4", + "acorn": "5.4.1", + "acorn-globals": "4.1.0", + "array-equal": "1.0.0", + "browser-process-hrtime": "0.1.2", + "content-type-parser": "1.0.2", + "cssom": "0.3.2", + "cssstyle": "0.2.37", + "domexception": "1.0.1", + "escodegen": "1.9.0", + "html-encoding-sniffer": "1.0.2", + "left-pad": "1.2.0", + "nwmatcher": "1.4.3", + "parse5": "4.0.0", + "pn": "1.1.0", + "request": "2.83.0", + "request-promise-native": "1.0.5", + "sax": "1.2.4", + "symbol-tree": "3.2.2", + "tough-cookie": "2.3.3", + "w3c-hr-time": "1.0.1", + "webidl-conversions": "4.0.2", + "whatwg-encoding": "1.0.3", + "whatwg-url": "6.4.0", + "ws": "4.0.0", + "xml-name-validator": "3.0.0" + }, + "dependencies": { + "ws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-4.0.0.tgz", + "integrity": "sha512-QYslsH44bH8O7/W2815u5DpnCpXWpEK44FmaHffNwgJI4JMaSZONgPBTOfrxJ29mXKbXak+LsJ2uAkDTYq2ptQ==", + "dev": true, + "requires": { + "async-limiter": "1.0.0", + "safe-buffer": "5.1.1", + "ultron": "1.1.0" + } + } + } + }, "jshint": { "version": "2.9.5", "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz", @@ -3214,6 +3425,89 @@ } } }, + "jsome": { + "version": "2.3.26", + "resolved": "https://registry.npmjs.org/jsome/-/jsome-2.3.26.tgz", + "integrity": "sha1-jLRDiSTSyd1SlMkK3wPzVBT7PKk=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "json-stringify-safe": "5.0.1", + "yargs": "4.8.1" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "1.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", + "dev": true + }, + "yargs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "dev": true, + "requires": { + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "lodash.assign": "4.2.0", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "window-size": "0.2.0", + "y18n": "3.2.1", + "yargs-parser": "2.4.1" + } + }, + "yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "dev": true, + "requires": { + "camelcase": "3.0.0", + "lodash.assign": "4.2.0" + } + } + } + }, "json-parse-better-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz", @@ -3239,6 +3533,12 @@ "jsonify": "0.0.0" } }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -3249,6 +3549,12 @@ "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=" }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, "jsonfile": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", @@ -3273,6 +3579,12 @@ "nomnom": "1.8.1" } }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3349,6 +3661,21 @@ } } }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "left-pad": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz", + "integrity": "sha1-0wpzxrggHY99jnlWupYWCHpo4O4=", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -3432,6 +3759,12 @@ "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, "lodash.create": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", @@ -3442,6 +3775,18 @@ "lodash._isiterateecall": "3.0.9" } }, + "lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", + "dev": true + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -3452,6 +3797,12 @@ "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=", + "dev": true + }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -3467,6 +3818,12 @@ "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "log-symbols": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz", @@ -3837,11 +4194,6 @@ } } }, - "moment": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", - "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3869,6 +4221,22 @@ "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.0.0.tgz", "integrity": "sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA=" }, + "node-cleanup": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", + "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", + "dev": true + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, "nomnom": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", @@ -3904,15 +4272,6 @@ } } }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1.1.0" - } - }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -3976,6 +4335,12 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "nwmatcher": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.3.tgz", + "integrity": "sha512-IKdSTiDWCarf2JTS5e9e2+5tPZGdkRJ79XjYV0pzK8Q9BpsFyBq1RGKxzs7Q8UBushGw7m6TzVKz6fcY99iSWw==", + "dev": true + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -4090,6 +4455,12 @@ "p-limit": "1.1.0" } }, + "parse-diff": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/parse-diff/-/parse-diff-0.4.0.tgz", + "integrity": "sha1-nONbzOj8C3xY9G1xETOU/AtJgt0=", + "dev": true + }, "parse-entities": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.1.1.tgz", @@ -4104,6 +4475,24 @@ "is-hexadecimal": "1.0.1" } }, + "parse-git-config": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-1.1.1.tgz", + "integrity": "sha1-06mYQxcTL1c5hxK7pDjhKVkN34w=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "fs-exists-sync": "0.1.0", + "git-config-path": "1.0.1", + "ini": "1.3.4" + } + }, + "parse-github-url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", + "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", + "dev": true + }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -4124,12 +4513,41 @@ "error-ex": "1.3.1" } }, + "parse-link-header": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-link-header/-/parse-link-header-1.0.1.tgz", + "integrity": "sha1-vt/g0hGK64S+deewJUGeyKYRQKc=", + "dev": true, + "requires": { + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, "parse-ms": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", "dev": true }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, "parsejson": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", @@ -4223,15 +4641,33 @@ "pinkie": "2.0.4" } }, + "pinpoint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pinpoint/-/pinpoint-1.1.0.tgz", + "integrity": "sha1-DPd1eml38b9/ajIge3CeN3OI6HQ=", + "dev": true + }, "platform": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.4.tgz", "integrity": "sha1-bw+xftqqSPIUQrOpdcBjEw8cPr0=" }, + "plur": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", + "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=", + "dev": true + }, "pluralize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-4.0.0.tgz", - "integrity": "sha1-WbcIwcAZCi9pLxx2GMRGsFL9F2I=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true }, "postcss": { @@ -4508,14 +4944,6 @@ "is-finite": "1.0.2", "parse-ms": "1.0.1", "plur": "1.0.0" - }, - "dependencies": { - "plur": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", - "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=", - "dev": true - } } }, "process-nextick-args": { @@ -4538,6 +4966,12 @@ "through2": "0.2.3" } }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -4676,6 +5110,12 @@ "string_decoder": "0.10.31" } }, + "readline-sync": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.7.tgz", + "integrity": "sha1-ABv91MBhEMPAhMY798alYCIhPzA=", + "dev": true + }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -4963,6 +5403,52 @@ } } }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, + "requires": { + "lodash": "4.17.5" + }, + "dependencies": { + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "dev": true + } + } + }, + "request-promise-native": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", + "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "dev": true, + "requires": { + "request-promise-core": "1.1.1", + "stealthy-require": "1.1.1", + "tough-cookie": "2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.1.tgz", + "integrity": "sha1-xUUjPp19pmFunVmt+zn8n1iGdv8=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, "require-uncached": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", @@ -4973,12 +5459,6 @@ "resolve-from": "1.0.1" } }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", @@ -5001,6 +5481,12 @@ "signal-exit": "3.0.2" } }, + "rfc6902": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/rfc6902/-/rfc6902-2.2.2.tgz", + "integrity": "sha512-/5JHC6Kr7EJnivq9M5O3WGnHBs5yCBJeQHB75IsBPPHDyaiuf0jsaQ4g0ehd5N+xPddPKGJH7rzp2AbaQdve3w==", + "dev": true + }, "rgb2hex": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.0.tgz", @@ -5058,13 +5544,20 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, "setprototypeof": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", @@ -5371,6 +5864,12 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -5791,23 +6290,6 @@ } } }, - "stylelint-config-standard": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-18.0.0.tgz", - "integrity": "sha1-DYcrQPr9zdz0GI+1tk3bOIforvw=", - "dev": true, - "requires": { - "stylelint-config-recommended": "2.0.1" - }, - "dependencies": { - "stylelint-config-recommended": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-2.0.1.tgz", - "integrity": "sha512-FXdgdOEGpaFQoKGhsi8IbsCI6dkxHQPa1CCqIybkN1d8LKtdxrn/A1rgu8DpJ6J+/4L30FOJeVdPttGfxCDHBQ==", - "dev": true - } - } - }, "sugarss": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-1.0.0.tgz", @@ -5823,7 +6305,7 @@ "integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=", "requires": { "debug": "2.6.7", - "es6-promise": "4.1.1" + "es6-promise": "4.2.2" } }, "supports-color": { @@ -5832,12 +6314,39 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, + "supports-hyperlinks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz", + "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", + "dev": true, + "requires": { + "has-flag": "2.0.0", + "supports-color": "5.1.0" + }, + "dependencies": { + "supports-color": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", + "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, "svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", "dev": true }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, "table": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", @@ -6023,6 +6532,23 @@ "punycode": "1.4.1" } }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "dev": true + } + } + }, "trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", @@ -6046,12 +6572,6 @@ "integrity": "sha1-qf2LA5Swro//guBjOgo2zK1bX4Y=", "dev": true }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -6106,12 +6626,6 @@ "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", "dev": true }, - "underscore.string": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.3.tgz", - "integrity": "sha1-gGmSYzZl1eX8tNsfs6hi62jp5to=", - "dev": true - }, "unherit": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.0.tgz", @@ -6234,6 +6748,12 @@ } } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6314,6 +6834,25 @@ "unist-util-stringify-position": "1.1.1" } }, + "vm2": { + "version": "github:patriksimek/vm2#7e82f90ac705fc44fad044147cb0df09b4c79a57", + "dev": true + }, + "voca": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/voca/-/voca-1.4.0.tgz", + "integrity": "sha512-8Xz4H3vhYRGbFupLtl6dHwMx0ojUcjt0HYkqZ9oBCfipd/5mD7Md58m2/dq7uPuZU/0T3Gb1m66KS9jn+I+14Q==", + "dev": true + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "0.1.2" + } + }, "walk": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.9.tgz", @@ -6498,12 +7037,38 @@ } } }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, "wgxpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wgxpath/-/wgxpath-1.0.0.tgz", "integrity": "sha1-7vikudVYzEla06mit1FZfs2a9pA=", "dev": true }, + "whatwg-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz", + "integrity": "sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.19" + } + }, + "whatwg-url": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.4.0.tgz", + "integrity": "sha512-Z0CVh/YE217Foyb488eo+iBv+r7eAQ0wSTyApi9n06jhcA3z6Nidg/EGvl0UFkg7kMdKxfBzzr+o9JF+cevgMg==", + "dev": true, + "requires": { + "lodash.sortby": "4.7.0", + "tr46": "1.0.1", + "webidl-conversions": "4.0.2" + } + }, "which": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", @@ -6519,6 +7084,16 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -6583,6 +7158,12 @@ "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.0.0.tgz", "integrity": "sha1-iYr7k4abJGYc+cUvnujbjtB2Tdk=" }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, "xmlhttprequest-ssl": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", @@ -6596,6 +7177,12 @@ "object-keys": "0.4.0" } }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", diff --git a/package.json b/package.json index 61fbcb61..5180e653 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,18 @@ { "name": "magicmirror", - "version": "2.2.2", + "version": "2.3.0", "description": "The open source modular smart mirror platform.", "main": "js/electron.js", "scripts": { "start": "sh run-start.sh", - "install": "cd vendor && npm install", - "install-fonts": "cd fonts && npm install", - "postinstall": "sh installers/postinstall/postinstall.sh && npm run install-fonts", + "install": "cd vendor && yon install", + "install-fonts": "cd fonts && yon install", + "postinstall": "sh installers/postinstall/postinstall.sh && yon run install-fonts", "test": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests --recursive", "test:unit": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/unit --recursive", "test:e2e": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/e2e --recursive", - "config:check": "node tests/configs/check_config.js" + "config:check": "node tests/configs/check_config.js", + "lint": "grunt" }, "repository": { "type": "git", @@ -36,6 +37,7 @@ "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "current-week-number": "^1.0.7", + "danger": "^3.1.3", "grunt": "latest", "grunt-eslint": "latest", "grunt-jsonlint": "latest", @@ -43,18 +45,20 @@ "grunt-stylelint": "latest", "grunt-yamllint": "latest", "http-auth": "^3.2.3", + "jsdom": "^11.6.2", "jshint": "^2.9.5", "mocha": "^4.1.0", "mocha-each": "^1.1.0", "spectron": "3.7.x", "stylelint": "^8.4.0", "stylelint-config-standard": "latest", - "time-grunt": "latest" + "time-grunt": "latest", + "yarn-or-npm": "^2.0.4" }, "dependencies": { "body-parser": "^1.18.2", "colors": "^1.1.2", - "electron": "1.4.15", + "electron": "^1.7.13", "express": "^4.16.2", "express-ipfilter": "0.3.1", "feedme": "latest", diff --git a/tests/configs/check_config.js b/tests/configs/check_config.js index c4690223..baee1caf 100644 --- a/tests/configs/check_config.js +++ b/tests/configs/check_config.js @@ -14,8 +14,6 @@ var path = require("path"); var fs = require("fs"); var Utils = require(__dirname + "/../../js/utils.js"); -if (process.env.NODE_ENV == "test") { return 0 }; - /* getConfigFile() * Return string with path of configuration file * Check if set by enviroment variable MM_CONFIG_FILE @@ -30,37 +28,43 @@ function getConfigFile() { return configFileName; } -var configFileName = getConfigFile(); -// Check if file is present -if (fs.existsSync(configFileName) === false) { - console.error(Utils.colors.error("File not found: "), configFileName); - return; -} -// check permision -try { - fs.accessSync(configFileName, fs.F_OK); -} catch (e) { - console.log(Utils.colors.error(e)); - return; -} - -// Validate syntax of the configuration file. -// In case the there errors show messages and -// return -console.info(Utils.colors.info("Checking file... ", configFileName)); -// I'm not sure if all ever is utf-8 -fs.readFile(configFileName, "utf-8", function (err, data) { - if (err) { throw err; } - v.JSHINT(data); // Parser by jshint - - if (v.JSHINT.errors.length == 0) { - console.log("Your configuration file doesn't contain syntax errors :)"); - return true; - } else { - errors = v.JSHINT.data().errors; - for (idx in errors) { - error = errors[idx]; - console.log("Line", error.line, "col", error.character, error.reason); - } +function checkConfigFile() { + var configFileName = getConfigFile(); + // Check if file is present + if (fs.existsSync(configFileName) === false) { + console.error(Utils.colors.error("File not found: "), configFileName); + return; } -}); + // check permision + try { + fs.accessSync(configFileName, fs.F_OK); + } catch (e) { + console.log(Utils.colors.error(e)); + return; + } + + // Validate syntax of the configuration file. + // In case the there errors show messages and + // return + console.info(Utils.colors.info("Checking file... ", configFileName)); + // I'm not sure if all ever is utf-8 + fs.readFile(configFileName, "utf-8", function (err, data) { + if (err) { throw err; } + v.JSHINT(data); // Parser by jshint + + if (v.JSHINT.errors.length == 0) { + console.log("Your configuration file doesn't contain syntax errors :)"); + return true; + } else { + errors = v.JSHINT.data().errors; + for (idx in errors) { + error = errors[idx]; + console.log("Line", error.line, "col", error.character, error.reason); + } + } + }); +} + +if (process.env.NODE_ENV !== "test") { + checkConfigFile(); +}; \ No newline at end of file diff --git a/tests/configs/data/StripComments.json b/tests/configs/data/StripComments.json new file mode 100644 index 00000000..62d5d618 --- /dev/null +++ b/tests/configs/data/StripComments.json @@ -0,0 +1,13 @@ +{ + // Escaped + "FOO\"BAR": "Today", + + /* + * The following lines + * represent cardinal directions + */ + "N": "N", + "E": "E", + "S": "S", + "W": "W" +} diff --git a/tests/configs/data/TranslationTest.json b/tests/configs/data/TranslationTest.json new file mode 100644 index 00000000..5614b23c --- /dev/null +++ b/tests/configs/data/TranslationTest.json @@ -0,0 +1,32 @@ +{ + "LOADING": "Loading …", + + "TODAY": "Today", + "TOMORROW": "Tomorrow", + "DAYAFTERTOMORROW": "In 2 days", + "RUNNING": "Ends in", + "EMPTY": "No upcoming events.", + + "WEEK": "Week {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "UPDATE_NOTIFICATION": "MagicMirror² update available.", + "UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.", + "UPDATE_INFO": "The current installation is COMMIT_COUNT behind on the BRANCH_NAME branch." +} diff --git a/tests/configs/data/en.json b/tests/configs/data/en.json new file mode 100644 index 00000000..5614b23c --- /dev/null +++ b/tests/configs/data/en.json @@ -0,0 +1,32 @@ +{ + "LOADING": "Loading …", + + "TODAY": "Today", + "TOMORROW": "Tomorrow", + "DAYAFTERTOMORROW": "In 2 days", + "RUNNING": "Ends in", + "EMPTY": "No upcoming events.", + + "WEEK": "Week {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "UPDATE_NOTIFICATION": "MagicMirror² update available.", + "UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.", + "UPDATE_INFO": "The current installation is COMMIT_COUNT behind on the BRANCH_NAME branch." +} diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js new file mode 100644 index 00000000..727f4623 --- /dev/null +++ b/tests/e2e/translations_spec.js @@ -0,0 +1,129 @@ +const fs = require("fs"); +const path = require("path"); +const chai = require("chai"); +const expect = chai.expect; +const mlog = require("mocha-logger"); +const translations = require("../../translations/translations.js"); +const helmet = require("helmet"); +const {JSDOM} = require("jsdom"); +const express = require("express"); + +describe("Translations", function() { + let server; + + before(function() { + const app = express(); + app.use(helmet()); + app.use(function (req, res, next) { + res.header("Access-Control-Allow-Origin", "*"); + next(); + }); + app.use("/translations", express.static(path.join(__dirname, "..", "..", "translations"))); + + server = app.listen(3000); + }); + + after(function() { + server.close(); + }); + + it("should have a translation file in the specified path", function() { + for(let language in translations) { + const file = fs.statSync(translations[language]); + expect(file.isFile()).to.be.equal(true); + } + }); + + const mmm = { + name: "TranslationTest", + file(file) { + return `http://localhost:3000/${file}`; + } + }; + + describe("Parsing language files through the Translator class", function() { + for(let language in translations) { + it(`should parse ${language}`, function(done) { + const dom = new JSDOM(`\ + \ + \ + \ + \ + \ + \ + \ + \ +