From d82f3d0f0d74691b91cd8dfa6516b86972388c12 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 6 Mar 2017 15:26:44 -0700 Subject: [PATCH 01/56] Added initial recaptcha + RL support --- client/coral-admin/src/translations.json | 6 +- errors.js | 8 +- models/user.js | 20 + package.json | 3 +- services/passport.js | 262 ++++++++- services/users.js | 104 +++- test/services/users.js | 12 +- yarn.lock | 673 ++++++++++++----------- 8 files changed, 694 insertions(+), 394 deletions(-) diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index cebc499ca..ecc8c4b95 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -1,7 +1,8 @@ { "en": { "errors": { - "NOT_AUTHORIZED": "Your username or password is not recognized by our system." + "NOT_AUTHORIZED": "Your username or password is not recognized by our system.", + "LOGIN_MAXIMUM_EXCEEDED": "You have made too many unsuccessful password attempts. Please wait." }, "community": { "username_and_email": "Username and Email", @@ -139,7 +140,8 @@ }, "es": { "errors": { - "NOT_AUTHORIZED": "Acción no autorizada." + "NOT_AUTHORIZED": "Acción no autorizada.", + "LOGIN_MAXIMUM_EXCEEDED": "Ha realizado demasiados intentos fallidos de contraseña. Por favor espera." }, "community": { "username_and_email": "Usuario y E-mail", diff --git a/errors.js b/errors.js index 6d9abbcfd..6cd8d8da0 100644 --- a/errors.js +++ b/errors.js @@ -148,6 +148,11 @@ const ErrPermissionUpdateUsername = new APIError('You do not have permission to status: 500 }); +const ErrLoginAttemptMaximumExceeded = new APIError('You have made too many incorrect password attempts.', { + translation_key: 'LOGIN_MAXIMUM_EXCEEDED', + status: 429 +}); + module.exports = { ExtendableError, APIError, @@ -168,5 +173,6 @@ module.exports = { ErrNotAuthorized, ErrPermissionUpdateUsername, ErrSettingsInit, - ErrInstallLock + ErrInstallLock, + ErrLoginAttemptMaximumExceeded }; diff --git a/models/user.js b/models/user.js index 2b65bd300..e23acaa56 100644 --- a/models/user.js +++ b/models/user.js @@ -1,4 +1,5 @@ const mongoose = require('../services/mongoose'); +const bcrypt = require('bcrypt'); const uuid = require('uuid'); // USER_ROLES is the array of roles that is permissible as a user role. @@ -146,6 +147,25 @@ UserSchema.method('hasRoles', function(...roles) { }); }); +/** + * This verifies that a password is valid. + */ +UserSchema.method('verifyPassword', function(password) { + return new Promise((resolve, reject) => { + bcrypt.compare(password, this.password, (err, res) => { + if (err) { + return reject(err); + } + + if (!res) { + return resolve(false); + } + + return resolve(true); + }); + }); +}); + /** * All the graph operations that are available for a user. * @type {Array} diff --git a/package.json b/package.json index f67a36908..48eb71f74 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "env-rewrite": "^1.0.2", "express": "^4.14.0", "express-session": "^1.14.2", + "form-data": "^2.1.2", "graphql": "^0.8.2", "graphql-errors": "^2.1.0", "graphql-server-express": "^0.5.0", @@ -76,6 +77,7 @@ "mongoose": "^4.6.5", "morgan": "^1.7.0", "natural": "^0.4.0", + "node-fetch": "^1.6.3", "nodemailer": "^2.6.4", "parse-duration": "^0.1.1", "passport": "^0.3.2", @@ -136,7 +138,6 @@ "mocha": "^3.1.2", "mocha-junit-reporter": "^1.12.1", "nightwatch": "^0.9.11", - "node-fetch": "^1.6.3", "nodemon": "^1.11.0", "postcss-loader": "^1.1.0", "postcss-modules": "^0.5.2", diff --git a/services/passport.js b/services/passport.js index 229541521..4ec80bb60 100644 --- a/services/passport.js +++ b/services/passport.js @@ -1,9 +1,12 @@ const passport = require('passport'); const UsersService = require('./users'); const SettingsService = require('./settings'); +const fetch = require('node-fetch'); +const FormData = require('form-data'); const LocalStrategy = require('passport-local').Strategy; const FacebookStrategy = require('passport-facebook').Strategy; const errors = require('../errors'); +const debug = require('debug')('talk:passport'); //============================================================================== // SESSION SERIALIZATION @@ -74,27 +77,233 @@ function ValidateUserLogin(loginProfile, user, done) { // STRATEGIES //============================================================================== +/** + * This looks at the request headers to see if there is a recaptcha response on + * the input request. + */ +const CheckIfRecaptcha = (req) => { + let response = req.get('X-Recaptcha-Response'); + + if (response && response.length > 0) { + return true; + } + + return false; +}; + +/** + * This checks the user to see if the current email profile needs to get checked + * for recaptcha compliance before being allowed to login. + */ +const CheckIfNeedsRecaptcha = (user, email) => { + + // Get the profile representing the local account. + let profile = user.profiles.find((profile) => profile.id === email); + + // This should never get to this point, if it does, don't let this past. + if (!profile) { + throw new Error('ID indicated by loginProfile is not on user object'); + } + + if (profile.metadata && profile.metadata.recaptcha_required) { + return true; + } + + return false; +}; + +/** + * This stores the Recaptcha secret. + */ +const RECAPTCHA_SECRET = process.env.TALK_RECAPTCHA_SECRET; + +/** + * This is true when the recaptcha secret is provided and the Recaptcha feature + * is to be enabled. + */ +const RECAPTCHA_ENABLED = RECAPTCHA_SECRET && RECAPTCHA_SECRET.length > 0; +if (!RECAPTCHA_ENABLED) { + console.log('Recaptcha is not enabled for login/signup abuse prevention, set TALK_RECAPTCHA_SECRET to enable Recaptcha.'); +} + +/** + * This sends the request details down Google to check to see if the response is + * genuine or not. + * @return {Promise} resolves with the success status of the recaptcha + */ +const CheckRecaptcha = async (req) => { + + // Ask Google to verify the recaptcha response: https://developers.google.com/recaptcha/docs/verify + const form = new FormData(); + + form.append('secret', RECAPTCHA_SECRET); + form.append('response', req.get('X-Recaptcha-Response')); + form.append('remoteip', req.ip); + + // Perform the request. + let res = await fetch('https://www.google.com/recaptcha/api/siteverify', { + method: 'POST', + body: form, + headers: form.getHeaders() + }); + + // Parse the JSON response. + let json = await res.json(); + + return json.success; +}; + +/** + * This records a login attempt failure as well as optionally flags an account + * for requiring a recaptcha in the future outside the temporary window. + * @return {Promise} resolves with nothing if rate limit not exeeded, errors if + * there is a rate limit error + */ +const HandleFailedAttempt = async (email, userNeedsRecaptcha) => { + try { + await UsersService.recordLoginAttempt(email); + } catch (err) { + if (err === errors.ErrLoginAttemptMaximumExceeded && !userNeedsRecaptcha && RECAPTCHA_ENABLED) { + + debug(`flagging user email=${email}`); + await UsersService.flagForRecaptchaRequirement(email, true); + } + + throw err; + } +}; + passport.use(new LocalStrategy({ usernameField: 'email', - passwordField: 'password' -}, (email, password, done) => { - UsersService - .findLocalUser(email, password) - .then((user) => { - if (!user) { - return done(null, false, {message: 'Incorrect email/password combination'}); + passwordField: 'password', + passReqToCallback: true +}, async (req, email, password, done) => { + + // We need to check if this request has a recaptcha on it at all, if it does, + // we must verify it first. If verification fails, we fail the request early. + // We can only do this obviously when recaptcha is enabled. + let hasRecaptcha = CheckIfRecaptcha(req); + let recaptchaPassed = false; + if (RECAPTCHA_ENABLED && hasRecaptcha) { + + try { + + // Check to see if this recaptcha passed. + recaptchaPassed = await CheckRecaptcha(req); + } catch (err) { + return done(err); + } + + if (!recaptchaPassed) { + try { + await HandleFailedAttempt(email); + } catch (err) { + return done(err); } - // Define the loginProfile being used to perform an additional - // verificaiton. - let loginProfile = {id: email, provider: 'local'}; + return done(null, false, {message: 'Incorrect recaptcha'}); + } + } - // Validate the user login. - return ValidateUserLogin(loginProfile, user, done); - }) - .catch((err) => { - done(err); - }); + debug(`hasRecaptcha=${hasRecaptcha}, recaptchaPassed=${recaptchaPassed}`); + + // If the request didn't have a recaptcha, check to see if we did need one by + // checking the rate limit against failed attempts on this email + // address/login. + if (!hasRecaptcha) { + try { + await UsersService.checkLoginAttempts(email); + } catch (err) { + if (err === errors.ErrLoginAttemptMaximumExceeded) { + + // This says, we didn't have a recaptcha, yet we needed one.. Reject + // here. + + try { + await HandleFailedAttempt(email); + } catch (err) { + return done(err); + } + + return done(null, false, {message: 'Incorrect recaptcha'}); + } + + // Some other unexpected error occured. + return done(err); + } + } + + // Let's find the user for which this login is connected to. + let user; + try { + user = await UsersService.findLocalUser(email); + } catch (err) { + return done(err); + } + + debug(`user=${user != null}`); + + // If the user doesn't exist, then mark this as a failed attempt at logging in + // this non-existant user and continue. + if (!user) { + try { + await HandleFailedAttempt(email); + } catch (err) { + return done(err); + } + + return done(null, false, {message: 'Incorrect email/password combination'}); + } + + // Let's check if the user indeed needed recaptcha in order to authenticate. + // We can only do this obviously when recaptcha is enabled. + let userNeedsRecaptcha = false; + if (RECAPTCHA_ENABLED && user) { + userNeedsRecaptcha = CheckIfNeedsRecaptcha(user, email); + } + + debug(`userNeedsRecaptcha=${userNeedsRecaptcha}`); + + // Let's check now if their password is correct. + let userPasswordCorrect; + try { + userPasswordCorrect = await user.verifyPassword(password); + } catch (err) { + return done(err); + } + + debug(`userPasswordCorrect=${userPasswordCorrect}`); + + // If their password wasn't correct, mark their attempt as failed and + // continue. + if (!userPasswordCorrect) { + try { + await HandleFailedAttempt(email, userNeedsRecaptcha); + } catch (err) { + return done(err); + } + + return done(null, false, {message: 'Incorrect email/password combination'}); + } + + // If the user needed a recaptcha, yet we have gotten this far, this indicates + // that the password was correct, so let's unflag their account for logins. We + // can only do this obviously when recaptcha is enabled. The account wouldn't + // have been flagged otherwise. + if (RECAPTCHA_ENABLED && userNeedsRecaptcha) { + try { + await UsersService.flagForRecaptchaRequirement(email, false); + } catch (err) { + return done(err); + } + } + + // Define the loginProfile being used to perform an additional + // verificaiton. + let loginProfile = {id: email, provider: 'local'}; + + // Perform final steps to login the user. + return ValidateUserLogin(loginProfile, user, done); })); if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && process.env.TALK_ROOT_URL) { @@ -102,17 +311,18 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && clientID: process.env.TALK_FACEBOOK_APP_ID, clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, - + passReqToCallback: true, profileFields: ['id', 'displayName', 'picture.type(large)'] - }, (accessToken, refreshToken, profile, done) => { - UsersService - .findOrCreateExternalUser(profile) - .then((user) => { - return ValidateUserLogin(profile, user, done); - }) - .catch((err) => { - done(err); - }); + }, async (req, accessToken, refreshToken, profile, done) => { + + let user; + try { + user = await UsersService.findOrCreateExternalUser(profile); + } catch (err) { + return done(err); + } + + return ValidateUserLogin(profile, user, done); })); } else { console.error('Facebook cannot be enabled, missing one of TALK_FACEBOOK_APP_ID, TALK_FACEBOOK_APP_SECRET, TALK_ROOT_URL'); diff --git a/services/users.js b/services/users.js index b58776a75..1810cf2a5 100644 --- a/services/users.js +++ b/services/users.js @@ -3,11 +3,16 @@ const jwt = require('jsonwebtoken'); const Wordlist = require('./wordlist'); const errors = require('../errors'); const uuid = require('uuid'); +const redis = require('./redis'); +const redisClient = redis.createClient(); const UserModel = require('../models/user'); const USER_STATUS = require('../models/user').USER_STATUS; const USER_ROLES = require('../models/user').USER_ROLES; +const RECAPTCHA_WINDOW_SECONDS = 60 * 10; // 10 minutes. +const RECAPTCHA_INCORRECT_TRIGGER = 5; // after 3 incorrect attempts, recaptcha will be required. + const ActionsService = require('./actions'); // In the event that the TALK_SESSION_SECRET is missing but we are testing, then @@ -30,13 +35,9 @@ const SALT_ROUNDS = 10; module.exports = class UsersService { /** - * Finds a user given their email address that we have for them in the system - * and ensures that the retuned user matches the password passed in as well. - * @param {string} email - email to look up the user by - * @param {string} password - password to match against the found user - * @param {Function} done [description] + * Returns a user (if found) for the given email address. */ - static findLocalUser(email, password) { + static findLocalUser(email) { if (!email || typeof email !== 'string') { return Promise.reject('email is required for findLocalUser'); } @@ -48,32 +49,99 @@ module.exports = class UsersService { provider: 'local' } } - }) - .then((user) => { - if (!user) { - return false; - } + }); + } - return new Promise((resolve, reject) => { - bcrypt.compare(password, user.password, (err, res) => { + /** + * This records an unsucesfull login attempt for the given email address. If + * the maximum has been reached, the promise will be rejected with: + * + * errors.ErrLoginAttemptMaximumExceeded + * + * Indicating that the account should be flagged as "login recaptcha required" + * where future login attempts must be made with the recaptcha flag. + */ + static recordLoginAttempt(email) { + const rdskey = `la[${email.toLowerCase().trim()}]`; + + return new Promise((resolve, reject) => { + redisClient + .multi() + .incr(rdskey) + .expire(rdskey, RECAPTCHA_WINDOW_SECONDS) + .exec((err, replies) => { if (err) { return reject(err); } - if (!res) { - return resolve(false); + // if this is new or has no expiry + if (replies[0] === 1 || replies[1] === -1) { + + // then expire it after the timeout + redisClient.expire(rdskey, RECAPTCHA_WINDOW_SECONDS); } - return resolve(user); + if (replies[0] >= RECAPTCHA_INCORRECT_TRIGGER) { + return reject(errors.ErrLoginAttemptMaximumExceeded); + } + + resolve(); }); - }); + }); + } + + /** + * This checks to see if the current login attempts against a user exeeds the + * maximum value allowed, if so, it rejects with: + * + * errors.ErrLoginAttemptMaximumExceeded + */ + static checkLoginAttempts(email) { + const rdskey = `la[${email.toLowerCase().trim()}]`; + + return new Promise((resolve, reject) => { + redisClient + .get(rdskey, (err, reply) => { + if (err) { + return reject(err); + } + + if (!reply) { + return resolve(); + } + + if (reply >= RECAPTCHA_INCORRECT_TRIGGER) { + return reject(errors.ErrLoginAttemptMaximumExceeded); + } + + resolve(); + }); + }); + } + + /** + * Sets or unsets the recaptcha_required flag on a user's local profile. + */ + static flagForRecaptchaRequirement(email, required) { + return UserModel.update({ + profiles: { + $elemMatch: { + id: email.toLowerCase(), + provider: 'local' + } + } + }, { + $set: { + 'profiles.$.metadata.recaptcha_required': required + } }); } /** * Merges two users together by taking all the profiles on a given user and * pushing them into the source user followed by deleting the destination user's - * user account. This will not merge the roles associated with the source user. + * user account. This will + * not merge the roles associated with the source user. * @param {String} dstUserID id of the user to which is the target of the merge * @param {String} srcUserID id of the user to which is the source of the merge * @return {Promise} resolves when the users are merged diff --git a/test/services/users.js b/test/services/users.js index 51ba01403..94aa92432 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -64,22 +64,14 @@ describe('services.UsersService', () => { describe('#findLocalUser', () => { - it('should find a user when we give the right credentials', () => { + it('should find a user', () => { return UsersService - .findLocalUser(mockUsers[0].profiles[0].id, '1Coral!-') + .findLocalUser(mockUsers[0].profiles[0].id) .then((user) => { expect(user).to.have.property('username', mockUsers[0].username); }); }); - it('should not find the user when we give the wrong credentials', () => { - return UsersService - .findLocalUser(mockUsers[0].profiles[0].id, '1Coral!-') - .then((user) => { - expect(user).to.equal(false); - }); - }); - }); describe('#createLocalUser', () => { diff --git a/yarn.lock b/yarn.lock index 82b5e92f3..f4de3a813 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,5 +1,7 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 + + "@kadira/storybook-deployer@^1.1.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@kadira/storybook-deployer/-/storybook-deployer-1.2.0.tgz#1708f5cb37fa08ab4173b1bd99df6f4717dfae12" @@ -289,14 +291,14 @@ assert@^1.1.1: dependencies: util "0.10.3" -assertion-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" - assertion-error@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.0.tgz#c7f85438fdd466bc7ca16ab90c81513797a5d23b" +assertion-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + ast-traverse@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ast-traverse/-/ast-traverse-0.1.1.tgz#69cf2b8386f19dcda1bb1e05d68fe359d8897de6" @@ -313,11 +315,15 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" -async@^1.4.0, async@^1.4.2, async@^1.5.2, async@1.x: +async@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0" + +async@1.x, async@^1.4.0, async@^1.4.2, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.2, async@^2.1.4, async@2.1.4: +async@2.1.4, async@^2.1.2, async@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" dependencies: @@ -331,10 +337,6 @@ async@~0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" -async@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1053,6 +1055,10 @@ babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: version "6.15.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" +balanced-match@0.1.0, balanced-match@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a" + balanced-match@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.2.1.tgz#7bc658b4bed61eee424ad74f75f5c3e2c4df3cc7" @@ -1061,10 +1067,6 @@ balanced-match@^0.4.1, balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" -balanced-match@~0.1.0, balanced-match@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a" - base64-js@^1.0.2: version "1.2.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1" @@ -1073,7 +1075,7 @@ base64-url@1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/base64-url/-/base64-url-1.3.3.tgz#f8b6c537f09a4fc58c99cb86e0b0e9c61461a20f" -base64url@^2.0.0, base64url@2.0.0: +base64url@2.0.0, base64url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" @@ -1119,10 +1121,6 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^2.10.2: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" - bluebird@2.10.2: version "2.10.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.10.2.tgz#024a5517295308857f14f91f1106fc3b555f446b" @@ -1139,6 +1137,10 @@ bluebird@3.4.6: version "3.4.6" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.6.tgz#01da8d821d87813d158967e743d5fe6c62cf8c0f" +bluebird@^2.10.2: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" @@ -1383,7 +1385,7 @@ chai@^3.5.0: deep-eql "^0.1.3" type-detect "^1.0.0" -chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3, chalk@1.1.3: +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -1542,7 +1544,7 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-table@^0.3.1, cli-table@0.3.1: +cli-table@0.3.1, cli-table@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" dependencies: @@ -1626,18 +1628,18 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@~0.6.0-1: - version "0.6.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" - -colors@~1.1.2, colors@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" +colors@1.1.2, colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + +colors@~0.6.0-1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -1650,16 +1652,6 @@ combined-stream@~0.0.4: dependencies: delayed-stream "0.0.5" -commander@^2.5.0, commander@^2.9.0, commander@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - dependencies: - graceful-readlink ">= 1.0.0" - -commander@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781" - commander@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" @@ -1678,6 +1670,16 @@ commander@2.8.x: dependencies: graceful-readlink ">= 1.0.0" +commander@2.9.0, commander@^2.5.0, commander@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +commander@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781" + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -1805,7 +1807,7 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" -cookiejar@^2.0.6, cookiejar@2.0.x: +cookiejar@2.0.x, cookiejar@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.0.6.tgz#0abf356ad00d1c5a219d88d44518046dd026acfe" @@ -1830,7 +1832,7 @@ core-js@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" -core-util-is@~1.0.0, core-util-is@1.0.2: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2020,7 +2022,7 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" -"cssom@>= 0.3.0 < 0.4.0", cssom@0.3.x: +cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0": version "0.3.2" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" @@ -2045,16 +2047,16 @@ cz-conventional-changelog@1.1.5: dependencies: word-wrap "^1.0.3" +d3-helpers@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/d3-helpers/-/d3-helpers-0.3.0.tgz#4b31dce4a2121a77336384574d893fbed5fb293d" + d@^0.1.1, d@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" dependencies: es5-ext "~0.10.2" -d3-helpers@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/d3-helpers/-/d3-helpers-0.3.0.tgz#4b31dce4a2121a77336384574d893fbed5fb293d" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2085,17 +2087,13 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@*, debug@^2.1.1, debug@^2.2.0, debug@2, debug@2.6.0: +debug@*, debug@2, debug@2.6.0, debug@^2.1.1, debug@^2.2.0: version "2.6.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" dependencies: ms "0.7.2" -debug@^0.7.2, debug@~0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" - -debug@~2.2.0, debug@2.2.0: +debug@2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -2107,11 +2105,15 @@ debug@2.3.3: dependencies: ms "0.7.2" +debug@^0.7.2, debug@~0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" -deep-eql@^0.1.3, deep-eql@0.1.3: +deep-eql@0.1.3, deep-eql@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" dependencies: @@ -2177,14 +2179,14 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - delayed-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.5.tgz#d4b1f43a93e8296dfe02694f4680bc37a313c73f" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -2241,7 +2243,7 @@ dns-prefetch-control@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" -doctrine@^1.2.2, doctrine@1.5.0: +doctrine@1.5.0, doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" dependencies: @@ -2252,7 +2254,7 @@ doctypes@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" -dom-serializer@~0.1.0, dom-serializer@0: +dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" dependencies: @@ -2263,21 +2265,21 @@ domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" +domelementtype@1, domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + domelementtype@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" -domelementtype@~1.1.1, domelementtype@1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - -domhandler@^2.3.0, domhandler@2.3: +domhandler@2.3, domhandler@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" dependencies: domelementtype "1" -domutils@^1.5.1, domutils@1.5, domutils@1.5.1: +domutils@1.5, domutils@1.5.1, domutils@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: @@ -2361,18 +2363,18 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" -end-of-stream@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" - dependencies: - once "~1.3.0" - end-of-stream@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e" dependencies: once "~1.3.0" +end-of-stream@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" + dependencies: + once "~1.3.0" + enhanced-resolve@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.0.3.tgz#df14c06b5fc5eecade1094c9c5a12b4b3edc0b62" @@ -2382,14 +2384,14 @@ enhanced-resolve@^3.0.0: object-assign "^4.0.1" tapable "^0.2.5" -entities@^1.1.1, entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" - entities@1.0: version "1.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" +entities@^1.1.1, entities@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + env-rewrite@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/env-rewrite/-/env-rewrite-1.0.2.tgz#3e344a95af1bdaab34a559479b8be3abd0804183" @@ -2463,14 +2465,14 @@ es6-map@^0.1.3: es6-symbol "~3.1.0" event-emitter "~0.3.4" -es6-promise@^3.0.2: - version "3.3.1" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" - es6-promise@3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4" +es6-promise@^3.0.2: + version "3.3.1" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" + es6-set@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8" @@ -2485,7 +2487,7 @@ es6-shim@^0.35.3: version "0.35.3" resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.3.tgz#9bfb7363feffff87a6cdb6cd93e405ec3c4b6f26" -es6-symbol@~3.1, es6-symbol@~3.1.0, es6-symbol@3: +es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" dependencies: @@ -2509,15 +2511,15 @@ escape-regexp-component@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/escape-regexp-component/-/escape-regexp-component-1.0.2.tgz#9c63b6d0b25ff2a88c3adbd18c5b61acc3b9faa2" -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5, escape-string-regexp@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - escape-string-regexp@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" -escodegen@^1.6.1, escodegen@1.x.x: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@1.x.x, escodegen@^1.6.1: version "1.8.1" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" dependencies: @@ -2653,14 +2655,14 @@ esprima-fb@~15001.1001.0-dev-harmony-fb: version "15001.1001.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659" +esprima@3.x.x, esprima@~3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + esprima@^2.6.0, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" -esprima@~3.1.0, esprima@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - esrecurse@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" @@ -2785,14 +2787,14 @@ express@^4.12.2, express@^4.14.0: utils-merge "1.0.0" vary "~1.1.0" +extend@3, extend@^3.0.0, extend@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" + extend@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/extend/-/extend-1.3.0.tgz#d1516fb0ff5624d2ebf9123ea1dac5a1994004f8" -extend@^3.0.0, extend@~3.0.0, extend@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" - external-editor@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.1.tgz#4c597c6c88fa6410e41dbbaa7b1be2336aa31095" @@ -2961,6 +2963,14 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data@1.0.0-rc4: + version "1.0.0-rc4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e" + dependencies: + async "^1.5.2" + combined-stream "^1.0.5" + mime-types "^2.1.10" + form-data@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-0.2.0.tgz#26f8bc26da6440e299cbdcfb69035c4f77a6e466" @@ -2969,7 +2979,7 @@ form-data@^0.2.0: combined-stream "~0.0.4" mime-types "~2.0.3" -form-data@~2.1.1: +form-data@^2.1.2, form-data@~2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" dependencies: @@ -2977,14 +2987,6 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" -form-data@1.0.0-rc4: - version "1.0.0-rc4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e" - dependencies: - async "^1.5.2" - combined-stream "^1.0.5" - mime-types "^2.1.10" - formidable@^1.0.17: version "1.1.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" @@ -3199,37 +3201,6 @@ glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" -glob@^5.0.15, glob@^5.0.3: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467" @@ -3259,6 +3230,37 @@ glob@7.0.5, glob@7.0.x: once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^5.0.15, glob@^5.0.3: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + globals@^9.0.0, globals@^9.14.0: version "9.14.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034" @@ -3600,15 +3602,15 @@ https-proxy-agent@1: debug "2" extend "3" -iconv-lite@^0.4.13, iconv-lite@^0.4.5, iconv-lite@~0.4.13, iconv-lite@0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" - iconv-lite@0.4.13: version "0.4.13" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" -icss-replace-symbols@^1.0.2, icss-replace-symbols@1.0.2: +iconv-lite@0.4.15, iconv-lite@^0.4.13, iconv-lite@^0.4.5, iconv-lite@~0.4.13: + version "0.4.15" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" + +icss-replace-symbols@1.0.2, icss-replace-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5" @@ -3666,7 +3668,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@2, inherits@2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -3685,7 +3687,24 @@ inquirer-confirm@0.2.2: bluebird "2.9.24" inquirer "0.8.2" -inquirer@^0.12.0, inquirer@0.12.0: +inquirer@0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.11.1.tgz#623b6e0c101d2fe9e8e8ed902e651e0519d143e5" + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^1.0.1" + figures "^1.3.5" + lodash "^3.3.1" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + strip-ansi "^3.0.0" + through "^2.3.6" + +inquirer@0.12.0, inquirer@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" dependencies: @@ -3703,6 +3722,19 @@ inquirer@^0.12.0, inquirer@0.12.0: strip-ansi "^3.0.0" through "^2.3.6" +inquirer@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.8.2.tgz#41586548e1c5d9b3f81df7325034baacab6f58ab" + dependencies: + ansi-regex "^1.1.1" + chalk "^1.0.0" + cli-width "^1.0.1" + figures "^1.3.5" + lodash "^3.3.1" + readline2 "^0.1.1" + rx "^2.4.3" + through "^2.3.6" + inquirer@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.1.tgz#6dfbffaf4d697dd76c8fe349f919de01c28afc4d" @@ -3721,36 +3753,6 @@ inquirer@^3.0.1: strip-ansi "^3.0.0" through "^2.3.6" -inquirer@0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.11.1.tgz#623b6e0c101d2fe9e8e8ed902e651e0519d143e5" - dependencies: - ansi-escapes "^1.1.0" - ansi-regex "^2.0.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^1.0.1" - figures "^1.3.5" - lodash "^3.3.1" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - strip-ansi "^3.0.0" - through "^2.3.6" - -inquirer@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.8.2.tgz#41586548e1c5d9b3f81df7325034baacab6f58ab" - dependencies: - ansi-regex "^1.1.1" - chalk "^1.0.0" - cli-width "^1.0.1" - figures "^1.3.5" - lodash "^3.3.1" - readline2 "^0.1.1" - rx "^2.4.3" - through "^2.3.6" - interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" @@ -3998,14 +4000,14 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" -isarray@^1.0.0, isarray@~1.0.0, isarray@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + isemail@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a" @@ -4146,7 +4148,7 @@ js-tokens@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" -js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@~3.7.0, js-yaml@3.x: +js-yaml@3.x, js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" dependencies: @@ -4420,7 +4422,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@^0.2.7, loader-utils@~0.2.2, loader-utils@0.2.x: +loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@^0.2.7, loader-utils@~0.2.2: version "0.2.16" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" dependencies: @@ -4540,7 +4542,7 @@ lodash.cond@^4.3.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" -lodash.create@^3.1.1, lodash.create@3.1.1: +lodash.create@3.1.1, lodash.create@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" dependencies: @@ -4633,7 +4635,7 @@ lodash.pickby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" -lodash.reduce@^4.4.0, lodash.reduce@4.6.0: +lodash.reduce@4.6.0, lodash.reduce@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" @@ -4659,18 +4661,18 @@ lodash.words@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@^3.3.1, lodash@3.10.1: +lodash@3.10.1, lodash@^3.3.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.2, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - lodash@3.9.3: version "3.9.3" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.9.3.tgz#0159e86832feffc6d61d852b12a953b99496bd32" +lodash@^4.0.0, lodash@^4.1.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.2, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + log4js@*: version "1.1.0" resolved "https://registry.yarnpkg.com/log4js/-/log4js-1.1.0.tgz#c7d2b616d91bbf47cc65fb79d6fe04581c8096fa" @@ -4693,7 +4695,7 @@ lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" -lru-cache@^2.5.0, lru-cache@2: +lru-cache@2, lru-cache@^2.5.0: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" @@ -4774,7 +4776,7 @@ metascraper@^1.0.6: popsicle "^6.2.0" to-title-case "^1.0.0" -methods@^1.1.1, methods@^1.1.2, methods@~1.1.2, methods@1.x: +methods@1.x, methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -4823,7 +4825,7 @@ mime-types@~2.0.3: dependencies: mime-db "~1.12.0" -mime@^1.3.4, mime@1.3.4: +mime@1.3.4, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" @@ -4835,7 +4837,7 @@ minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, "minimatch@2 || 3": +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" dependencies: @@ -4848,11 +4850,7 @@ minimatch@~0.2.11, minimatch@~0.2.14: lru-cache "2" sigmund "~1.0.0" -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -minimist@~0.0.1, minimist@0.0.8: +minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -4860,11 +4858,9 @@ minimist@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.0.tgz#cdf225e8898f840a258ded44fc91776770afdc93" -mkdirp@^0.5.0, mkdirp@^0.5.1, "mkdirp@>=0.5 0", mkdirp@~0.5.0, mkdirp@~0.5.1, mkdirp@0.5.1, mkdirp@0.5.x: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" mkdirp@0.3.0: version "0.3.0" @@ -4876,6 +4872,12 @@ mkdirp@0.5.0: dependencies: minimist "0.0.8" +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + mkpath@>=0.1.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-1.0.0.tgz#ebb3a977e7af1c683ae6fda12b545a6ba6c5853d" @@ -4920,10 +4922,6 @@ mocha@^3.1.2: mkdirp "0.5.1" supports-color "3.1.2" -moment@^2.10.3, moment@2.x.x: - version "2.17.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" - moment@2.12.0: version "2.12.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.12.0.tgz#dc2560d19838d6c0731b1a6afa04675264d360d6" @@ -4932,6 +4930,10 @@ moment@2.17.0: version "2.17.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.0.tgz#a4c292e02aac5ddefb29a6eed24f51938dd3b74f" +moment@2.x.x, moment@^2.10.3: + version "2.17.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" + mongodb-core@2.1.7: version "2.1.7" resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.7.tgz#6a27909b98142ef2508d924c274969008954fa29" @@ -4991,14 +4993,14 @@ mquery@2.2.1: regexp-clone "0.0.1" sliced "0.0.5" -ms@^0.7.1, ms@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" - ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" +ms@0.7.2, ms@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + muri@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/muri/-/muri-1.2.0.tgz#b86383c902920b09ebe62af0e75c94de5f33cd3d" @@ -5015,7 +5017,7 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -nan@2.5.0: +nan@2.5.0, nan@^2.3.0: version "2.5.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.0.tgz#aa8f1e34531d807e9e27755b234b4a6ec0c152a8" @@ -5212,15 +5214,15 @@ nodemon@^1.11.0: undefsafe "0.0.3" update-notifier "0.5.0" -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" +nopt@3.x, nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" dependencies: abbrev "1" -nopt@~3.0.6, nopt@3.x: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" dependencies: abbrev "1" @@ -5366,7 +5368,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -optimist@^0.6.1, optimist@>=0.3.5, optimist@0.6.1: +optimist@0.6.1, optimist@>=0.3.5, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" dependencies: @@ -5542,16 +5544,16 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" dependencies: isarray "0.0.1" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -5857,33 +5859,33 @@ postcss-mixins@^2.1.0: postcss "^5.0.10" postcss-simple-vars "^1.0.1" -postcss-modules-extract-imports@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz#8fb3fef9a6dd0420d3f6d4353cf1ff73f2b2a341" - dependencies: - postcss "^5.0.4" - postcss-modules-extract-imports@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.0.tgz#5b07f368e350cda6fd5c8844b79123a7bd3e37be" dependencies: postcss "^5.0.4" -postcss-modules-local-by-default@^1.0.1, postcss-modules-local-by-default@1.1.1: +postcss-modules-extract-imports@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz#8fb3fef9a6dd0420d3f6d4353cf1ff73f2b2a341" + dependencies: + postcss "^5.0.4" + +postcss-modules-local-by-default@1.1.1, postcss-modules-local-by-default@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.1.1.tgz#29a10673fa37d19251265ca2ba3150d9040eb4ce" dependencies: css-selector-tokenizer "^0.6.0" postcss "^5.0.4" -postcss-modules-scope@^1.0.0, postcss-modules-scope@1.0.2: +postcss-modules-scope@1.0.2, postcss-modules-scope@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.0.2.tgz#ff977395e5e06202d7362290b88b1e8cd049de29" dependencies: css-selector-tokenizer "^0.6.0" postcss "^5.0.4" -postcss-modules-values@^1.1.0, postcss-modules-values@1.2.2: +postcss-modules-values@1.2.2, postcss-modules-values@^1.1.0: version "1.2.2" resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.2.2.tgz#f0e7d476fe1ed88c5e4c7f97533a3e772ad94ca1" dependencies: @@ -6049,6 +6051,14 @@ postcss-zindex@^2.0.1: postcss "^5.0.4" uniqs "^2.0.0" +postcss@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.1.2.tgz#bd84886a66bcad489afaf7c673eed5ef639551e2" + dependencies: + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.1.2" + postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.19, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.1.2, postcss@^5.2.11, postcss@^5.2.4, postcss@^5.2.5, postcss@^5.2.9: version "5.2.11" resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.11.tgz#ff29bcd6d2efb98bfe08a022055ec599bbe7b761" @@ -6058,14 +6068,6 @@ postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0. source-map "^0.5.6" supports-color "^3.2.3" -postcss@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.1.2.tgz#bd84886a66bcad489afaf7c673eed5ef639551e2" - dependencies: - js-base64 "^2.1.9" - source-map "^0.5.6" - supports-color "^3.1.2" - pre-git@^3.10.0: version "3.12.0" resolved "https://registry.yarnpkg.com/pre-git/-/pre-git-3.12.0.tgz#8291899a15ba86f9cdaa8ca180e47096deff8dc3" @@ -6130,7 +6132,7 @@ process@^0.11.0: version "0.11.9" resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1" -progress@^1.1.8, progress@1.1.8: +progress@1.1.8, progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -6283,22 +6285,18 @@ pug@^2.0.0-beta3: pug-runtime "^2.0.3" pug-strip-comments "^1.0.2" -punycode@^1.2.4, punycode@^1.4.1, punycode@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" +punycode@1.4.1, punycode@^1.2.4, punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + pym.js@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pym.js/-/pym.js-1.1.1.tgz#3b5eb9e8499e5c95e5cb8fe29888ea7edf10a507" -q@^1.1.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" - q@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/q/-/q-1.1.2.tgz#6357e291206701d99f197ab84e57e8ad196f2a89" @@ -6311,7 +6309,15 @@ q@2.0.3: pop-iterate "^1.0.1" weak-map "^1.0.5" -qs@^6.1.0, qs@^6.2.0, qs@6.2.1: +q@^1.1.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" + +qs@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" + +qs@6.2.1, qs@^6.1.0, qs@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" @@ -6319,10 +6325,6 @@ qs@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" -qs@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" - query-string@^4.1.0, query-string@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.1.tgz#54baada6713eafc92be75c47a731f2ebd09cd11d" @@ -6342,14 +6344,14 @@ quote@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/quote/-/quote-0.4.0.tgz#10839217f6c1362b89194044d29b233fd7f32f01" -ramda@^0.22.1: - version "0.22.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.22.1.tgz#031da0c3df417c5b33c96234757eb37033f36a0e" - ramda@0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.9.1.tgz#cc914dc3a82c608d003090203787c3f6826c1d87" +ramda@^0.22.1: + version "0.22.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.22.1.tgz#031da0c3df417c5b33c96234757eb37033f36a0e" + random-bytes@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" @@ -6405,7 +6407,7 @@ react-apollo@^0.10.0: optionalDependencies: react-dom "0.14.x || 15.* || ^15.0.0" -react-dom@^15.3.1, "react-dom@0.14.x || 15.* || ^15.0.0", react-dom@15.3.2: +"react-dom@0.14.x || 15.* || ^15.0.0", react-dom@15.3.2, react-dom@^15.3.1: version "15.3.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.3.2.tgz#c46b0aa5380d7b838e7a59c4a7beff2ed315531f" @@ -6467,7 +6469,7 @@ react-tagsinput@^3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/react-tagsinput/-/react-tagsinput-3.14.0.tgz#6729f8d5b313ed8fbb35496205ec2a97654af6bc" -react@^15.3.1, react@15.3.2: +react@15.3.2, react@^15.3.1: version "15.3.2" resolved "https://registry.yarnpkg.com/react/-/react-15.3.2.tgz#a7bccd2fee8af126b0317e222c28d1d54528d09e" dependencies: @@ -6503,7 +6505,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^1.1.7, readable-stream@1.1, readable-stream@1.1.x: +readable-stream@1.1, readable-stream@1.1.x, readable-stream@^1.1.7: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" dependencies: @@ -6512,7 +6514,7 @@ readable-stream@^1.1.7, readable-stream@1.1, readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.2.2, readable-stream@2: +readable-stream@2, readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" dependencies: @@ -6524,7 +6526,7 @@ readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2. string_decoder "~0.10.x" util-deprecate "~1.0.1" -readable-stream@^2.0.5, readable-stream@~2.1.4, readable-stream@2.1.5: +readable-stream@2.1.5, readable-stream@^2.0.5, readable-stream@~2.1.4: version "2.1.5" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" dependencies: @@ -6560,15 +6562,6 @@ readline2@^1.0.1: is-fullwidth-code-point "^1.0.0" mute-stream "0.0.5" -recast@^0.11.17: - version "0.11.20" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.20.tgz#2cb9bec269c03b36d0598118a936cd0a293ca3f3" - dependencies: - ast-types "0.9.4" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - recast@0.10.33: version "0.10.33" resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697" @@ -6578,6 +6571,15 @@ recast@0.10.33: private "~0.1.5" source-map "~0.5.0" +recast@^0.11.17: + version "0.11.20" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.20.tgz#2cb9bec269c03b36d0598118a936cd0a293ca3f3" + dependencies: + ast-types "0.9.4" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -6741,7 +6743,7 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@^2.55.0, request@^2.74.0, request@^2.79.0, request@2.79.0: +request@2.79.0, request@^2.55.0, request@^2.74.0, request@^2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: @@ -6766,13 +6768,6 @@ request@^2.55.0, request@^2.74.0, request@^2.79.0, request@2.79.0: tunnel-agent "~0.4.1" uuid "^3.0.0" -require_optional@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.0.tgz#52a86137a849728eb60a55533617f8f914f59abf" - dependencies: - resolve-from "^2.0.0" - semver "^5.1.0" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -6796,6 +6791,13 @@ require-uncached@^1.0.2: caller-path "^0.1.0" resolve-from "^1.0.0" +require_optional@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.0.tgz#52a86137a849728eb60a55533617f8f914f59abf" + dependencies: + resolve-from "^2.0.0" + semver "^5.1.0" + resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" @@ -6832,7 +6834,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@^2.2.8, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@~2.5.1, rimraf@~2.5.4, rimraf@2: +rimraf@2, rimraf@^2.2.8, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@~2.5.1, rimraf@~2.5.4: version "2.5.4" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" dependencies: @@ -6874,14 +6876,14 @@ safe-buffer@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" -sax@^1.1.4, sax@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" - sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" +sax@^1.1.4, sax@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + selenium-standalone@^5.11.2: version "5.11.2" resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-5.11.2.tgz#724ccaa72fb26f3711e0e20989e478c4133df844" @@ -6908,18 +6910,18 @@ semver-regex@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" -semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@~5.3.0, "semver@2 || 3 || 4 || 5": +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" -semver@~5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" - semver@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.0.tgz#85f2cf8550465c4df000cf7d86f6b054106ab9e5" +semver@~5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" + send@0.14.2: version "0.14.2" resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef" @@ -6969,6 +6971,10 @@ sha.js@^2.3.6: dependencies: inherits "^2.0.1" +shelljs@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.0.tgz#ce1ed837b4b0e55b5ec3dab84251ab9dbdc0c7ec" + shelljs@^0.7.0, shelljs@^0.7.5: version "0.7.6" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad" @@ -6977,10 +6983,6 @@ shelljs@^0.7.0, shelljs@^0.7.5: interpret "^1.0.0" rechoir "^0.6.2" -shelljs@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.0.tgz#ce1ed837b4b0e55b5ec3dab84251ab9dbdc0c7ec" - sigmund@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" @@ -7055,7 +7057,7 @@ socks-proxy-agent@2: extend "3" socks "~1.1.5" -socks@~1.1.5, socks@1.1.9: +socks@1.1.9, socks@~1.1.5: version "1.1.9" resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.9.tgz#628d7e4d04912435445ac0b6e459376cb3e6d691" dependencies: @@ -7078,7 +7080,13 @@ source-map-support@^0.4.2: dependencies: source-map "^0.5.3" -source-map@^0.4.4, source-map@0.4.x: +source-map@0.1.x: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" + dependencies: + amdefine ">=0.0.4" + +source-map@0.4.x, source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: @@ -7094,12 +7102,6 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" -source-map@0.1.x: - version "0.1.43" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" - dependencies: - amdefine ">=0.0.4" - spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" @@ -7200,10 +7202,6 @@ strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" -string_decoder@^0.10.25, string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - string-hash@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.1.tgz#8e85bed291e0763b8f6809d9c3368fea048db3dc" @@ -7229,6 +7227,10 @@ string-width@^2.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^3.0.0" +string_decoder@^0.10.25, string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + stringmap@~0.2.2: version "0.2.2" resolved "http://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz#556c137b258f942b8776f5b2ef582aa069d7d1b1" @@ -7277,7 +7279,7 @@ style-loader@^0.13.1: dependencies: loader-utils "^0.2.7" -stylus@~0.54.5, stylus@0.54.5: +stylus@0.54.5, stylus@~0.54.5: version "0.54.5" resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.5.tgz#42b9560931ca7090ce8515a798ba9e6aa3d6dc79" dependencies: @@ -7316,6 +7318,16 @@ supertest@^2.0.1: methods "1.x" superagent "^2.0.0" +supports-color@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" + +supports-color@3.1.2, supports-color@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" + dependencies: + has-flag "^1.0.0" + supports-color@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" @@ -7324,22 +7336,12 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" - dependencies: - has-flag "^1.0.0" - supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" -supports-color@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" - svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -7427,7 +7429,7 @@ text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -through@^2.3.6, through@~2.3, through@~2.3.1, through@~2.3.8, through@2: +through@2, through@^2.3.6, through@~2.3, through@~2.3.1, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -7564,14 +7566,14 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" - type-detect@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" +type-detect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" + type-is@~1.6.14: version "1.6.14" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2" @@ -7608,7 +7610,7 @@ uid-number@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" -uid-safe@~2.1.3, uid-safe@2.1.3: +uid-safe@2.1.3, uid-safe@~2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.3.tgz#077e264a00b3187936b270bb7376a26473631071" dependencies: @@ -7645,7 +7647,7 @@ uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" -unpipe@~1.0.0, unpipe@1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -7682,7 +7684,7 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@^0.10.3, util@0.10.3: +util@0.10.3, util@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: @@ -7802,7 +7804,7 @@ whatwg-encoding@^1.0.1: dependencies: iconv-lite "0.4.13" -whatwg-fetch@^2.0.0, whatwg-fetch@>=0.10.0: +whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.2.tgz#fe294d1d89e36c5be8b3195057f2e4bc74fc980e" @@ -7827,24 +7829,28 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" -which@^1.1.1: - version "1.2.12" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" - dependencies: - isexe "^1.1.1" - which@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/which/-/which-1.1.1.tgz#9ce512459946166e12c083f08ec073380fc8cbbb" dependencies: is-absolute "^0.1.7" +which@^1.1.1: + version "1.2.12" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" + dependencies: + isexe "^1.1.1" + wide-align@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad" dependencies: string-width "^1.0.1" +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + window-size@^0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" @@ -7853,10 +7859,6 @@ window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - with@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/with/-/with-5.1.1.tgz#fa4daa92daf32c4ea94ed453c81f04686b575dfe" @@ -7864,10 +7866,14 @@ with@^5.0.0: acorn "^3.1.0" acorn-globals "^3.0.0" -word-wrap@^1.0.3, word-wrap@1.1.0: +word-wrap@1.1.0, word-wrap@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.1.0.tgz#356153d61d10610d600785c5d701288e0ae764a6" +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + wordwrap@^1.0.0, wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -7876,10 +7882,6 @@ wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -8037,4 +8039,3 @@ yauzl@^2.5.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" - From d5b46f219ed81cd1fa05c0eec0dd676de37c69aa Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 7 Mar 2017 11:54:44 -0500 Subject: [PATCH 02/56] Adding load more button. --- .../ModerationQueue/ModerationContainer.js | 16 ++++++-- .../ModerationQueue/ModerationQueue.js | 12 +++++- .../components/ModerationMenu.js | 15 ++------ .../coral-admin/src/graphql/queries/index.js | 38 ++++++++++++++++++- .../src/graphql/queries/loadMore.graphql | 13 +++++++ 5 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 client/coral-admin/src/graphql/queries/loadMore.graphql diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 25a7a9511..99a1ec656 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -21,7 +21,8 @@ import ModerationKeysModal from '../../components/ModerationKeysModal'; class ModerationContainer extends Component { state = { - selectedIndex: 0 + selectedIndex: 0, + sort: 'REVERSE_CHRONOLOGICAL' } componentWillMount() { @@ -53,6 +54,11 @@ class ModerationContainer extends Component { } + selectSort = (sort) => { + this.setState({sort}); + this.props.modQueueResort(sort); + } + componentWillUnmount() { key.unbind('s'); key.unbind('shift+/'); @@ -71,7 +77,7 @@ class ModerationContainer extends Component { } render () { - const {data, moderation, settings, assets, modQueueResort, onClose, ...props} = this.props; + const {data, moderation, settings, assets, onClose, ...props} = this.props; const providedAssetId = this.props.params.id; const activeTab = this.props.route.path === ':id' ? 'premod' : this.props.route.path; @@ -103,7 +109,8 @@ class ModerationContainer extends Component { premodCount={data.premodCount} rejectedCount={data.rejectedCount} flaggedCount={data.flaggedCount} - modQueueResort={modQueueResort} + selectSort={this.selectSort} + sort={this.state.sort} /> { +const ModerationQueue = ({comments, selectedIndex, singleView, loadMore, activeTab, sort, ...props}) => { return (
    @@ -20,7 +20,7 @@ const ModerationQueue = ({comments, selectedIndex, singleView, ...props}) => { key={i} index={i} comment={comment} - commentType={props.activeTab} + commentType={activeTab} selected={i === selectedIndex} suspectWords={props.suspectWords} actions={actionsMap[status]} @@ -33,6 +33,14 @@ const ModerationQueue = ({comments, selectedIndex, singleView, ...props}) => { : {lang.t('modqueue.emptyqueue')} }
+
); }; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index f9b0c266f..8d6a0e883 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -9,10 +9,6 @@ import {Link} from 'react-router'; const lang = new I18n(translations); class ModerationMenu extends Component { - state = { - sort: 'REVERSE_CHRONOLOGICAL', - } - static propTypes = { premodCount: PropTypes.number.isRequired, rejectedCount: PropTypes.number.isRequired, @@ -22,13 +18,8 @@ class ModerationMenu extends Component { }) } - selectSort = (sort) => { - this.setState({sort}); - this.props.modQueueResort(sort); - } - render() { - const {asset, premodCount, rejectedCount, flaggedCount} = this.props; + const {asset, premodCount, rejectedCount, flaggedCount, selectSort, sort} = this.props; const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod'; const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected'; const flagPath = asset ? `/admin/moderate/flagged/${asset.id}` : '/admin/moderate/flagged'; @@ -50,8 +41,8 @@ class ModerationMenu extends Component { this.selectSort(sort)}> + value={sort} + onChange={sort => selectSort(sort)}> diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js index c4a30ad44..13164bca2 100644 --- a/client/coral-admin/src/graphql/queries/index.js +++ b/client/coral-admin/src/graphql/queries/index.js @@ -1,6 +1,7 @@ import {graphql} from 'react-apollo'; import MOD_QUEUE_QUERY from './modQueueQuery.graphql'; +import MOD_QUEUE_LOAD_MORE from './loadMore.graphql'; import METRICS from './metricsQuery.graphql'; export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { @@ -14,7 +15,8 @@ export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { }, props: ({ownProps: {params: {id = null}}, data}) => ({ data, - modQueueResort: modQueueResort(id, data.fetchMore) + modQueueResort: modQueueResort(id, data.fetchMore), + loadMore: loadMore(data.fetchMore) }) }); @@ -30,6 +32,40 @@ export const getMetrics = graphql(METRICS, { } }); +export const loadMore = (fetchMore) => ({limit, cursor, sort, tab, asset_id}) => { + let statuses; + switch(tab) { + case 'premod': + statuses = ['PREMOD']; + break; + case 'flagged': + statuses = ['NONE', 'PREMOD']; + break; + case 'rejected': + statuses = ['REJECTED']; + break; + } + return fetchMore({ + query: MOD_QUEUE_LOAD_MORE, + variables: { + limit, + cursor, + sort, + statuses, + asset_id + }, + updateQuery: (oldData, {fetchMoreResult:{data:{comments}}}) => { + return { + ...oldData, + [tab]: [ + ...oldData[tab], + ...comments + ] + }; + } + }); +}; + export const modQueueResort = (id, fetchMore) => (sort) => { return fetchMore({ query: MOD_QUEUE_QUERY, diff --git a/client/coral-admin/src/graphql/queries/loadMore.graphql b/client/coral-admin/src/graphql/queries/loadMore.graphql new file mode 100644 index 000000000..56966a804 --- /dev/null +++ b/client/coral-admin/src/graphql/queries/loadMore.graphql @@ -0,0 +1,13 @@ +#import "../fragments/commentView.graphql" + +query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!]) { + comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort}) { + ...commentView + action_summaries { + count + ... on FlagActionSummary { + reason + } + } + } +} From 54aa3bc8fa4a0e3fb0c67a16704409b22c7ba55f Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 7 Mar 2017 12:31:43 -0500 Subject: [PATCH 03/56] Styling load more button. --- .../ModerationQueue/ModerationContainer.js | 13 +++++++++++ .../ModerationQueue/ModerationQueue.js | 19 ++++++++-------- .../ModerationQueue/components/LoadMore.js | 22 +++++++++++++++++++ .../ModerationQueue/components/styles.css | 20 +++++++++++++++++ 4 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 99a1ec656..16de8a32e 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -100,6 +100,18 @@ class ModerationContainer extends Component { } const comments = data[activeTab]; + let activeTabCount; + switch(activeTab) { + case 'premod': + activeTabCount = data.premodCount; + break; + case 'flagged': + activeTabCount = data.flaggedCount; + break; + case 'rejected': + activeTabCount = data.rejectedCount; + break; + } return (
@@ -125,6 +137,7 @@ class ModerationContainer extends Component { loadMore={props.loadMore} assetId={providedAssetId} sort={this.state.sort} + commentCount={activeTabCount} /> { +const ModerationQueue = ({comments, selectedIndex, commentCount, singleView, loadMore, activeTab, sort, ...props}) => { return (
    @@ -33,14 +34,14 @@ const ModerationQueue = ({comments, selectedIndex, singleView, loadMore, activeT : {lang.t('modqueue.emptyqueue')} }
- +
); }; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js b/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js new file mode 100644 index 000000000..5ac193525 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js @@ -0,0 +1,22 @@ +import React from 'react'; +import {Button} from 'coral-ui'; +import styles from './styles.css'; + +const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) => +
+ { + showLoadMore && + } +
; + +export default LoadMore; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index 59f47faad..5abbb6b7d 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -387,3 +387,23 @@ span { cursor: pointer; } } + +.loadMoreContainer { + display: flex; + justify-content: center; + width: 100%; +}; + +.loadMore { + width: 100%; + text-align: center; + color: #FFF; + max-width: 660px; + margin-bottom: 30px; + background-color: #2376D8; + cursor: pointer; +} + +.loadMore:hover { + background-color: #4399FF; +} From 2a6236b61823525d181bbd4c97c25467d5cd7f35 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 7 Mar 2017 12:37:12 -0500 Subject: [PATCH 04/56] Moving ModerationMenu to a stateless compenent. --- .../components/ModerationMenu.js | 81 +++++++++---------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index 8d6a0e883..b8160e21b 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -1,4 +1,4 @@ -import React, {PropTypes, Component} from 'react'; +import React, {PropTypes} from 'react'; import CommentCount from './CommentCount'; import styles from './styles.css'; import {SelectField, Option} from 'react-mdl-selectfield'; @@ -8,48 +8,45 @@ import {Link} from 'react-router'; const lang = new I18n(translations); -class ModerationMenu extends Component { - static propTypes = { - premodCount: PropTypes.number.isRequired, - rejectedCount: PropTypes.number.isRequired, - flaggedCount: PropTypes.number.isRequired, - asset: PropTypes.shape({ - id: PropTypes.string - }) - } - - render() { - const {asset, premodCount, rejectedCount, flaggedCount, selectSort, sort} = this.props; - const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod'; - const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected'; - const flagPath = asset ? `/admin/moderate/flagged/${asset.id}` : '/admin/moderate/flagged'; - return ( -
-
-
-
- - {lang.t('modqueue.premod')} - - - {lang.t('modqueue.rejected')} - - - {lang.t('modqueue.flagged')} - -
- selectSort(sort)}> - - - +const ModerationMenu = ({asset, premodCount, rejectedCount, flaggedCount, selectSort, sort}) => { + const premodPath = asset ? `/admin/moderate/premod/${asset.id}` : '/admin/moderate/premod'; + const rejectPath = asset ? `/admin/moderate/rejected/${asset.id}` : '/admin/moderate/rejected'; + const flagPath = asset ? `/admin/moderate/flagged/${asset.id}` : '/admin/moderate/flagged'; + return ( +
+
+
+
+ + {lang.t('modqueue.premod')} + + + {lang.t('modqueue.rejected')} + + + {lang.t('modqueue.flagged')} +
+ selectSort(sort)}> + + +
- ); - } -} +
+ ); +}; + +ModerationMenu.propTypes = { + premodCount: PropTypes.number.isRequired, + rejectedCount: PropTypes.number.isRequired, + flaggedCount: PropTypes.number.isRequired, + asset: PropTypes.shape({ + id: PropTypes.string + }) +}; export default ModerationMenu; From 95aa89faed81ed5b1530d19f5ffb1a7acae877ec Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 7 Mar 2017 14:16:44 -0500 Subject: [PATCH 05/56] Switching premod status update to updateQueries function. --- .../src/graphql/mutations/index.js | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js index fe3a1faf9..884e30e0c 100644 --- a/client/coral-admin/src/graphql/mutations/index.js +++ b/client/coral-admin/src/graphql/mutations/index.js @@ -22,7 +22,26 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, { commentId, status: 'ACCEPTED' }, - refetchQueries: ['ModQueue'] + updateQueries: { + ModQueue: (oldData) => { + const premod = oldData.premod.filter(c => c.id !== commentId); + const flagged = oldData.flagged.filter(c => c.id !== commentId); + const rejected = oldData.rejected.filter(c => c.id !== commentId); + const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount; + const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount; + const rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount; + + return { + ...oldData, + premodCount, + flaggedCount, + rejectedCount, + premod, + flagged, + rejected, + }; + } + } }); }, rejectComment: ({commentId}) => { @@ -31,7 +50,27 @@ export const setCommentStatus = graphql(SET_COMMENT_STATUS, { commentId, status: 'REJECTED' }, - refetchQueries: ['ModQueue'] + updateQueries: { + ModQueue: (oldData) => { + const comment = oldData.premod.concat(oldData.flagged).filter(c => c.id === commentId)[0]; + const rejected = [comment].concat(oldData.rejected); + const premod = oldData.premod.filter(c => c.id !== commentId); + const flagged = oldData.flagged.filter(c => c.id !== commentId); + const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount; + const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount; + const rejectedCount = oldData.rejectedCount + 1; + + return { + ...oldData, + premodCount, + flaggedCount, + rejectedCount, + premod, + flagged, + rejected + }; + } + } }); } }) From e578f901b2c01cf463b4420a03f8334c32154c79 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 7 Mar 2017 13:38:01 -0700 Subject: [PATCH 06/56] fix some firefox display things --- .../coral-admin/src/containers/Configure/StreamSettings.js | 1 + client/coral-admin/src/containers/Dashboard/FlagWidget.js | 3 ++- client/coral-admin/src/containers/Dashboard/LikeWidget.js | 5 +++-- client/coral-admin/src/containers/Dashboard/Widget.css | 4 ++++ client/coral-admin/src/containers/Streams/Streams.js | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/StreamSettings.js b/client/coral-admin/src/containers/Configure/StreamSettings.js index 5530bc6cd..f77013bc0 100644 --- a/client/coral-admin/src/containers/Configure/StreamSettings.js +++ b/client/coral-admin/src/containers/Configure/StreamSettings.js @@ -171,6 +171,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
+ {/* the above card should be the last one if at all possible because of z-index issues with the selects */}
); }; diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index da71ac04a..5ce93f11b 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -3,6 +3,7 @@ import {Link} from 'react-router'; import styles from './Widget.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations'; +import range from 'lodash/range'; const lang = new I18n(translations); @@ -44,7 +45,7 @@ const FlagWidget = (props) => { } { /* rows in a table with a fixed height will expand and ignore height. this extra row will expand to fill the extra space. */ - assets.length < 10 ? : null + range(10 - Math.max(assets.length, 1)).map(() => ) } diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/containers/Dashboard/LikeWidget.js index 25e851966..39ee708f8 100644 --- a/client/coral-admin/src/containers/Dashboard/LikeWidget.js +++ b/client/coral-admin/src/containers/Dashboard/LikeWidget.js @@ -3,6 +3,7 @@ import {Link} from 'react-router'; import styles from './Widget.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations'; +import range from 'lodash/range'; const lang = new I18n(translations); @@ -44,8 +45,8 @@ const LikeWidget = (props) => { : {lang.t('dashboard.no_likes')} } { /* rows in a table with a fixed height will expand and ignore height. - this extra row will expand to fill the extra space. */ - assets.length < 10 ? : null + put in some extra rows. */ + range(10 - Math.max(assets.length, 1)).map(() => ) } diff --git a/client/coral-admin/src/containers/Dashboard/Widget.css b/client/coral-admin/src/containers/Dashboard/Widget.css index cb764d9cc..96c773bef 100644 --- a/client/coral-admin/src/containers/Dashboard/Widget.css +++ b/client/coral-admin/src/containers/Dashboard/Widget.css @@ -43,6 +43,10 @@ height: var(--row-height); } +.emptyRow { + height: var(--row-height); +} + .rowLinkify:last-child { border-bottom: none; } diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 96130fcba..bd5ce6e85 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -80,8 +80,8 @@ class Streams extends Component {
- {closed ? lang.t('streams.closed') : lang.t('streams.open')} {!statusMenuOpen && } + {closed ? lang.t('streams.closed') : lang.t('streams.open')}
{ statusMenuOpen && From 47f6f4cb6c1736f86105dee782c607df777f3fab Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 7 Mar 2017 16:51:22 -0700 Subject: [PATCH 07/56] styles and auto-update Dashboard --- .../src/containers/Dashboard/Dashboard.css | 25 ++++++++++ .../src/containers/Dashboard/Dashboard.js | 47 +++++++++++++++++-- .../src/containers/Dashboard/Widget.css | 10 ++-- client/coral-admin/src/translations.json | 4 ++ 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.css b/client/coral-admin/src/containers/Dashboard/Dashboard.css index 72b9bd90c..1bd1599b4 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.css +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.css @@ -7,3 +7,28 @@ font-size: 1.5rem; font-weight: bold; } + +.autoUpdate { + background-color: #ffdb87; + padding: 3px 10px 10px 10px; + margin-bottom: 0; + + i { + position: relative; + top: 7px; + } + + b { + float: right; + border-radius: 20px; + cursor: pointer; + background-color: #e6ba52; + width: 30px; + height: 30px; + text-align: center; + top: 4px; + position: relative; + line-height: 1.7em; + font-size: 1.3em; + } +} diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js index 96e837a9e..5b4a20282 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.js @@ -6,11 +6,41 @@ import {getMetrics} from 'coral-admin/src/graphql/queries'; import FlagWidget from './FlagWidget'; import LikeWidget from './LikeWidget'; import {showBanUserDialog, hideBanUserDialog} from 'coral-admin/src/actions/moderation'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-admin/src/translations'; +import {Spinner, Icon} from 'coral-ui'; -import {Spinner} from 'coral-ui'; +const lang = new I18n(translations); +const refreshIntervalSeconds = 60 * 5; class Dashboard extends React.Component { + state = { + noteHidden: false, + secondsUntilRefresh: refreshIntervalSeconds + } + + componentWillMount () { + setInterval(() => { // the countdown timer + let nextCount = this.state.secondsUntilRefresh - 1; + if (nextCount < 0) { + nextCount = refreshIntervalSeconds; + this.props.data.refetch(); + } + this.setState({secondsUntilRefresh: nextCount}); + }, 1000); + } + + formatTime = () => { + const minutes = Math.floor(this.state.secondsUntilRefresh / 60); + let seconds = (this.state.secondsUntilRefresh % 60).toString(); + if (seconds.length < 2) { + seconds = `0${seconds}`; + } + + return `${minutes}:${seconds}`; + } + render () { if (this.props.data && this.props.data.loading) { @@ -20,9 +50,18 @@ class Dashboard extends React.Component { const {data: {assetsByLike, assetsByFlag}} = this.props; return ( -
- - +
+

this.setState({noteHidden: true})}> + × + {lang.t('dashboard.next-update', this.formatTime())} {lang.t('dashboard.auto-update')} +

+
+ + +
); } diff --git a/client/coral-admin/src/containers/Dashboard/Widget.css b/client/coral-admin/src/containers/Dashboard/Widget.css index cb764d9cc..42e7e2056 100644 --- a/client/coral-admin/src/containers/Dashboard/Widget.css +++ b/client/coral-admin/src/containers/Dashboard/Widget.css @@ -3,12 +3,15 @@ } .widget { - box-sizing: border-box; margin: 10px 5px 5px 5px; box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2); - padding: 15px; flex: 1; background-color: white; + box-sizing: border-box; +} + +.widget * { + box-sizing: border-box; } .heading { @@ -16,12 +19,13 @@ padding-left: 10px; font-size: 1.5rem; font-weight: bold; + background-color: #e0e0e0; } .widgetTable { - width: 100%; border-collapse: collapse; user-select: none; + margin: 5px 15px 15px 15px; height: calc(var(--row-height) * 10); } diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index ac9131685..0278d37da 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -117,6 +117,8 @@ "write_message": "Write a message" }, "dashboard": { + "next-update": "{0} minutes until next update.", + "auto-update": "Data automatically updates every five minutes or when you Reload.", "no_flags": "There have been no flags in the last 5 minutes! Hooray!", "no_likes": "There have been no likes in the last 5 minutes. All quiet.", "flags": "Flags", @@ -235,6 +237,8 @@ "yes_ban_user": "Si, Suspendan el usuario" }, "dashbord": { + "next-update": "", + "auto-update": "", "no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!", "no_likes": "A nadie le ha gustado algún comentario en los últimos 5 minutos. Todo tranquilo.", "flags": "Marcados", From ec634fa3c0f1f1ea18621694c190fb4ddc040527 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 8 Mar 2017 11:12:44 -0700 Subject: [PATCH 08/56] add translations --- client/coral-admin/src/translations.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 0278d37da..257148837 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -237,8 +237,8 @@ "yes_ban_user": "Si, Suspendan el usuario" }, "dashbord": { - "next-update": "", - "auto-update": "", + "next-update": "{0} minutos hasta la siguiente actualización.", + "auto-update": "Los datos se actualizan automaticamente cada 5 minutos o cuando Recargas.", "no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!", "no_likes": "A nadie le ha gustado algún comentario en los últimos 5 minutos. Todo tranquilo.", "flags": "Marcados", From 3262d136b730d7b046984396fbdbb4ea6c4ee51e Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 8 Mar 2017 15:44:19 -0500 Subject: [PATCH 09/56] Resetting report menu on close. --- client/coral-plugin-flags/FlagButton.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index 537d899cd..45110f407 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -32,18 +32,30 @@ class FlagButton extends Component { if (flagged) { this.setState((prev) => prev.localPost ? {...prev, localPost: null, step: 0} : {...prev, localDelete: true}); deleteAction(localPost || flag.current_user.id); + } else if (this.state.showMenu){ + this.closeMenu(); } else { - this.setState({showMenu: !this.state.showMenu}); + this.setState({showMenu: true}); } } + closeMenu = () => { + this.setState({ + showMenu: false, + itemType: '', + reason: '', + message: '', + step: 0 + }); + } + onPopupContinue = () => { const {postFlag, postDontAgree, id, author_id} = this.props; const {itemType, reason, step, posted, message} = this.state; // Proceed to the next step or close the menu if we've reached the end if (step + 1 >= this.props.getPopupMenu.length) { - this.setState({showMenu: false}); + this.closeMenu(); } else { this.setState({step: step + 1}); } @@ -114,7 +126,7 @@ class FlagButton extends Component { } handleClickOutside () { - this.setState({showMenu: false}); + this.closeMenu(); } render () { From e4cdc73bf82569a55f4100e5a825e05e79c8123d Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 8 Mar 2017 13:53:39 -0700 Subject: [PATCH 10/56] fix for react-mdn-selectfield in FF --- client/coral-admin/src/containers/Configure/Configure.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/containers/Configure/Configure.css index 6cb623978..b1b1ebbaf 100644 --- a/client/coral-admin/src/containers/Configure/Configure.css +++ b/client/coral-admin/src/containers/Configure/Configure.css @@ -90,6 +90,11 @@ .configTimeoutSelect { display: inline-block; margin-left: 20px; + + i { /* fix for firefox and react-mdl-selectfield@0.2.0 */ + padding: 20px 0; + vertical-align: top; + } } .charCountTexfield { From d203a9afe0e7af60f47dd16e5e2c7123b489e30d Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 8 Mar 2017 16:06:34 -0500 Subject: [PATCH 11/56] Auto-rejecting comments on ban. --- client/coral-admin/src/components/BanUserDialog.js | 10 ++++++++-- .../containers/ModerationQueue/ModerationContainer.js | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/components/BanUserDialog.js b/client/coral-admin/src/components/BanUserDialog.js index b23d8f9d1..f6a7227b8 100644 --- a/client/coral-admin/src/components/BanUserDialog.js +++ b/client/coral-admin/src/components/BanUserDialog.js @@ -8,7 +8,13 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const BanUserDialog = ({open, handleClose, handleBanUser, user}) => ( +const onBanClick = (userId, commentId, handleBanUser, rejectComment) => (e) => { + e.preventDefault(); + handleBanUser({userId}) + .then(() => rejectComment({commentId})); +}; + +const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, commentId}) => ( ( -
diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 2f43911b1..1d716ff41 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -26,7 +26,7 @@ class ModerationContainer extends Component { componentWillMount() { const {toggleModal, singleView} = this.props; - + this.props.fetchSettings(); key('s', () => singleView()); key('shift+/', () => toggleModal(true)); @@ -140,8 +140,10 @@ class ModerationContainer extends Component { Date: Wed, 8 Mar 2017 14:16:20 -0700 Subject: [PATCH 12/56] incorrectly using dispatch now that we do pym directly --- client/coral-embed-stream/src/Embed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 1c52fb68c..0b9bc4fc5 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -256,7 +256,7 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ requestConfirmEmail: () => dispatch(requestConfirmEmail()), loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)), - addNotification: (type, text) => dispatch(addNotification(type, text)), + addNotification: (type, text) => addNotification(type, text), clearNotification: () => dispatch(clearNotification()), editName: (username) => dispatch(editName(username)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), From fac3d26c2278cbc92a009352ed9a295f097d3b83 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 8 Mar 2017 16:39:53 -0500 Subject: [PATCH 13/56] Closing ban dialog after ban is complete. --- client/coral-admin/src/components/BanUserDialog.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/components/BanUserDialog.js b/client/coral-admin/src/components/BanUserDialog.js index f6a7227b8..4f2296409 100644 --- a/client/coral-admin/src/components/BanUserDialog.js +++ b/client/coral-admin/src/components/BanUserDialog.js @@ -8,9 +8,10 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const onBanClick = (userId, commentId, handleBanUser, rejectComment) => (e) => { +const onBanClick = (userId, commentId, handleBanUser, rejectComment, handleClose) => (e) => { e.preventDefault(); handleBanUser({userId}) + .then(handleClose) .then(() => rejectComment({commentId})); }; @@ -35,7 +36,7 @@ const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, c -
From eaf035acd6ae4ccd1f065acd223a4cb06abd02d5 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 8 Mar 2017 14:51:50 -0700 Subject: [PATCH 14/56] simplify dashboard table rendering --- .../src/containers/Dashboard/FlagWidget.js | 44 +++++++------------ .../src/containers/Dashboard/LikeWidget.js | 44 +++++++------------ .../src/containers/Dashboard/Widget.css | 25 +++++------ 3 files changed, 44 insertions(+), 69 deletions(-) diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index 5ce93f11b..48f39fc9e 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -13,42 +13,30 @@ const FlagWidget = (props) => { return (

Articles with the most flags

- - - - - - - - +
+
+

{lang.t('streams.article')}

+

{lang.t('dashboard.flags')}

+
+
{ assets.length ? assets.map(asset => { const flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary'); return ( -
- - - +
+

{flagSummary ? flagSummary.actionCount : 0}

+ +

{asset.title}

+

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

+ +
); }) - : + :
{lang.t('dashboard.no_flags')}
} - { /* rows in a table with a fixed height will expand and ignore height. - this extra row will expand to fill the extra space. */ - range(10 - Math.max(assets.length, 1)).map(() => ) - } - -
{lang.t('streams.article')}{lang.t('dashboard.flags')}
- -

{asset.title}

-

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

- -
- -

{flagSummary ? flagSummary.actionCount : 0}

- -
{lang.t('dashboard.no_flags')}
+
+
); }; diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/containers/Dashboard/LikeWidget.js index 39ee708f8..e627daece 100644 --- a/client/coral-admin/src/containers/Dashboard/LikeWidget.js +++ b/client/coral-admin/src/containers/Dashboard/LikeWidget.js @@ -14,42 +14,30 @@ const LikeWidget = (props) => { return (

Articles with the most likes

- - - - - - - - +
+
+

{lang.t('streams.article')}

+

{lang.t('modqueue.likes')}

+
+
{ assets.length ? assets.map(asset => { const likeSummary = asset.action_summaries.find(s => s.type === 'LikeAssetActionSummary'); return ( -
- - - +
+

{likeSummary ? likeSummary.actionCount : 0}

+ +

{asset.title}

+

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

+ +
); }) - : + :
{lang.t('dashboard.no_likes')}
} - { /* rows in a table with a fixed height will expand and ignore height. - put in some extra rows. */ - range(10 - Math.max(assets.length, 1)).map(() => ) - } - -
{lang.t('streams.article')}{lang.t('modqueue.likes')}
- -

{asset.title}

-

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

- -
- -

{likeSummary ? likeSummary.actionCount : 0}

- -
{lang.t('dashboard.no_likes')}
+
+ ); }; diff --git a/client/coral-admin/src/containers/Dashboard/Widget.css b/client/coral-admin/src/containers/Dashboard/Widget.css index 96c773bef..16f86336b 100644 --- a/client/coral-admin/src/containers/Dashboard/Widget.css +++ b/client/coral-admin/src/containers/Dashboard/Widget.css @@ -1,5 +1,5 @@ :root { - --row-height: 80px; + --row-height: 50px; } .widget { @@ -19,20 +19,25 @@ } .widgetTable { - width: 100%; - border-collapse: collapse; user-select: none; height: calc(var(--row-height) * 10); } -.widgetTable thead th { +.widgetHead p { color: rgb(35, 102, 223); padding: 10px; text-align: left; text-transform: capitalize; + display: inline-block; + box-sizing: border-box; + margin-bottom: 0; } -.widgetTable thead th:last-child { +.widgetHead p:first-child { + width: 90%; +} + +.widgetHead p:last-child { width: 10%; } @@ -41,10 +46,7 @@ border-bottom: 1px solid lightgrey; color: #555; height: var(--row-height); -} - -.emptyRow { - height: var(--row-height); + padding: 10px; } .rowLinkify:last-child { @@ -55,10 +57,6 @@ background-color: #f8f8f8; } -.widgetTable tbody td { - padding: 10px; -} - .linkToAsset { display: block; text-decoration: none; @@ -85,4 +83,5 @@ color: #555; font-size: 1.3em; font-weight: 400; + float: right; } From b34758e8cce417ac96d3e25d8cf9813a070e27ff Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 8 Mar 2017 14:59:55 -0700 Subject: [PATCH 15/56] unused vars --- client/coral-admin/src/containers/Dashboard/FlagWidget.js | 1 - client/coral-admin/src/containers/Dashboard/LikeWidget.js | 1 - 2 files changed, 2 deletions(-) diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index 48f39fc9e..aab70ddaf 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -3,7 +3,6 @@ import {Link} from 'react-router'; import styles from './Widget.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations'; -import range from 'lodash/range'; const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/containers/Dashboard/LikeWidget.js index e627daece..9c72be02e 100644 --- a/client/coral-admin/src/containers/Dashboard/LikeWidget.js +++ b/client/coral-admin/src/containers/Dashboard/LikeWidget.js @@ -3,7 +3,6 @@ import {Link} from 'react-router'; import styles from './Widget.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations'; -import range from 'lodash/range'; const lang = new I18n(translations); From 377563c79882135a370ed869778768d0ee51754d Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 8 Mar 2017 17:02:47 -0500 Subject: [PATCH 16/56] Hiding rejected note on ban dialog for rejected comments. --- client/coral-admin/src/actions/moderation.js | 2 +- client/coral-admin/src/components/BanUserDialog.js | 4 ++-- .../src/containers/ModerationQueue/ModerationContainer.js | 5 +++-- .../src/containers/ModerationQueue/components/Comment.js | 2 +- client/coral-admin/src/reducers/moderation.js | 1 + 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index 5116700c7..75ec5fc09 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -4,5 +4,5 @@ export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open}); export const singleView = () => ({type: actions.SINGLE_VIEW}); // Ban User Dialog -export const showBanUserDialog = (user, commentId) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId}); +export const showBanUserDialog = (user, commentId, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, showRejectedNote}); export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog}); diff --git a/client/coral-admin/src/components/BanUserDialog.js b/client/coral-admin/src/components/BanUserDialog.js index b23d8f9d1..86d18d879 100644 --- a/client/coral-admin/src/components/BanUserDialog.js +++ b/client/coral-admin/src/components/BanUserDialog.js @@ -8,7 +8,7 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const BanUserDialog = ({open, handleClose, handleBanUser, user}) => ( +const BanUserDialog = ({open, handleClose, handleBanUser, user, showRejectedNote}) => ( (

{lang.t('bandialog.are_you_sure', user.name)}

- {lang.t('bandialog.note')} + {showRejectedNote && lang.t('bandialog.note')}
{props.comment.user.status === 'banned' ? diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js index 09a0b39e7..cf270543c 100644 --- a/client/coral-admin/src/reducers/moderation.js +++ b/client/coral-admin/src/reducers/moderation.js @@ -19,6 +19,7 @@ export default function moderation (state = initialState, action) { .merge({ user: Map(action.user), commentId: action.commentId, + showRejectedNote: action.showRejectedNote, banDialog: true }); case actions.SET_ACTIVE_TAB: From feaad2d6ca14e3c353b820c7803bdd25c3e2ffea Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 8 Mar 2017 19:01:08 -0700 Subject: [PATCH 17/56] Added loader, graphql --- graph/loaders/metrics.js | 49 ++++++++++++++++++++++++++++++++++- graph/resolvers/asset.js | 6 ++++- graph/resolvers/root_query.js | 4 +++ graph/typeDefs.graphql | 21 ++++++++++++--- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/graph/loaders/metrics.js b/graph/loaders/metrics.js index a6fe5afe4..d3c602de8 100644 --- a/graph/loaders/metrics.js +++ b/graph/loaders/metrics.js @@ -4,6 +4,52 @@ const {objectCacheKeyFn} = require('./util'); const ActionModel = require('../../models/action'); +/** + * Returns the assets which have had comments made within the last time period. + */ +const getCommentActivityMetrics = ({loaders: {Assets, Comments}}, {from, to, limit}) => { + let assetMetrics = []; + + return Comments.aggregate([ + {$match: { + parent_id: null, + created_at: { + $gt: from, + $lt: to + } + }}, + {$group: { + _id: '$asset_id', + commentCount: { + $sum: 1 + } + }}, + {$project: { + _id: false, + asset_id: '$_id', + commentCount: '$commentCount' + }}, + {$sort: { + commentCount: -1 + }}, + {$limit: limit} + ]) + .then((results) => { + results = assetMetrics; + + return Assets.getByID.loadMany(results.map((result) => result.asset_id)); + }) + .then((assets) => assets.map((asset, i) => { + + // We're leveraging the fact that the comments returned by the aggregation + // query are in the request order that we just made, it's what the + // Assets.getByID loader does. + asset.commentCount = assetMetrics[i].commentCount; + + return asset; + })); +}; + /** * Returns a list of assets with action metadata included on the models. */ @@ -211,7 +257,8 @@ module.exports = (context) => ({ get: ({from, to, sort, limit}) => getAssetMetrics(context, {from, to, sort, limit}) }, Comments: { - get: ({from, to, sort, limit}) => getCommentMetrics(context, {from, to, sort, limit}) + get: ({from, to, sort, limit}) => getCommentMetrics(context, {from, to, sort, limit}), + getActivity: ({from, to, limit}) => getCommentActivityMetrics(context, {from, to, limit}), } } }); diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index 2077b9e7a..c0cffd1ae 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -10,7 +10,11 @@ const Asset = { parent_id: null }); }, - commentCount({id}, _, {loaders: {Comments}}) { + commentCount({id, commentCount}, _, {loaders: {Comments}}) { + if (commentCount) { + return commentCount; + } + return Comments.countByAssetID.load(id); }, settings({settings = null}, _, {loaders: {Settings}}) { diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index bdcdcef78..083526eff 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -71,6 +71,10 @@ const RootQuery = { return null; } + if (sort === 'COMMENTS') { + return Comments.getActivity({from, to, limit}); + } + return Comments.get({from, to, sort, limit}); }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 6cb4cb046..2821952e4 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -476,6 +476,21 @@ enum USER_STATUS { APPROVED } +enum COMMENT_METRICS_SORT { + + # Represents a LikeAction. + LIKE + + # Represents a FlagAction. + FLAG + + # Represents a don't agree action. + DONTAGREE + + # Represents activity. + ACTIVITY +} + type RootQuery { # Site wide settings and defaults. @@ -507,7 +522,7 @@ type RootQuery { # Comment metrics related to user actions are saturated into the comments # returned. - commentMetrics(from: Date!, to: Date!, sort: ACTION_TYPE!, limit: Int = 10): [Comment!] + commentMetrics(from: Date!, to: Date!, sort: COMMENT_METRICS_SORT!, limit: Int = 10): [Comment!] } ################################################################################ @@ -646,14 +661,14 @@ type SetCommentStatusResponse implements Response { type AddCommentTagResponse implements Response { # An array of errors relating to the mutation that occured. comment: Comment - errors: [UserError] + errors: [UserError] } # Response to removeCommentTag mutation type RemoveCommentTagResponse implements Response { # An array of errors relating to the mutation that occured. comment: Comment - errors: [UserError] + errors: [UserError] } # All mutations for the application are defined on this object. From 63220cb6a721ea4b88221f420d38616c07152590 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 8 Mar 2017 19:14:02 -0700 Subject: [PATCH 18/56] Some fixes to make it work --- graph/loaders/metrics.js | 11 ++++++----- graph/resolvers/root_query.js | 10 ++++++---- graph/typeDefs.graphql | 7 ++++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/graph/loaders/metrics.js b/graph/loaders/metrics.js index d3c602de8..a0408ed4d 100644 --- a/graph/loaders/metrics.js +++ b/graph/loaders/metrics.js @@ -3,14 +3,15 @@ const DataLoader = require('dataloader'); const {objectCacheKeyFn} = require('./util'); const ActionModel = require('../../models/action'); +const CommentModel = require('../../models/comment'); /** * Returns the assets which have had comments made within the last time period. */ -const getCommentActivityMetrics = ({loaders: {Assets, Comments}}, {from, to, limit}) => { +const getAssetActivityMetrics = ({loaders: {Assets}}, {from, to, limit}) => { let assetMetrics = []; - return Comments.aggregate([ + return CommentModel.aggregate([ {$match: { parent_id: null, created_at: { @@ -35,7 +36,7 @@ const getCommentActivityMetrics = ({loaders: {Assets, Comments}}, {from, to, lim {$limit: limit} ]) .then((results) => { - results = assetMetrics; + assetMetrics = results; return Assets.getByID.loadMany(results.map((result) => result.asset_id)); }) @@ -254,11 +255,11 @@ module.exports = (context) => ({ cacheKeyFn: objectCacheKeyFn('from', 'to') }), Assets: { - get: ({from, to, sort, limit}) => getAssetMetrics(context, {from, to, sort, limit}) + get: ({from, to, sort, limit}) => getAssetMetrics(context, {from, to, sort, limit}), + getActivity: ({from, to, limit}) => getAssetActivityMetrics(context, {from, to, limit}), }, Comments: { get: ({from, to, sort, limit}) => getCommentMetrics(context, {from, to, sort, limit}), - getActivity: ({from, to, limit}) => getCommentActivityMetrics(context, {from, to, limit}), } } }); diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 083526eff..98dbfb7ed 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -63,6 +63,12 @@ const RootQuery = { return null; } + console.log({from, to, sort, limit}); + + if (sort === 'ACTIVITY') { + return Assets.getActivity({from, to, limit}); + } + return Assets.get({from, to, sort, limit}); }, @@ -71,10 +77,6 @@ const RootQuery = { return null; } - if (sort === 'COMMENTS') { - return Comments.getActivity({from, to, limit}); - } - return Comments.get({from, to, sort, limit}); }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 2821952e4..7e0cc534d 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -476,7 +476,8 @@ enum USER_STATUS { APPROVED } -enum COMMENT_METRICS_SORT { +# Metrics for the assets. +enum ASSET_METRICS_SORT { # Represents a LikeAction. LIKE @@ -518,11 +519,11 @@ type RootQuery { # Asset metrics related to user actions are saturated into the assets # returned. - assetMetrics(from: Date!, to: Date!, sort: ACTION_TYPE!, limit: Int = 10): [Asset!] + assetMetrics(from: Date!, to: Date!, sort: ASSET_METRICS_SORT!, limit: Int = 10): [Asset!] # Comment metrics related to user actions are saturated into the comments # returned. - commentMetrics(from: Date!, to: Date!, sort: COMMENT_METRICS_SORT!, limit: Int = 10): [Comment!] + commentMetrics(from: Date!, to: Date!, sort: ACTION_TYPE!, limit: Int = 10): [Comment!] } ################################################################################ From a0953c068906209a3265d1d5458185a35f372ac0 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 9 Mar 2017 13:33:00 -0500 Subject: [PATCH 19/56] Adding proptypes to loadmore component. --- .../containers/ModerationQueue/components/LoadMore.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js b/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js index 5ac193525..f8d3f0ed4 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/LoadMore.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import {Button} from 'coral-ui'; import styles from './styles.css'; @@ -19,4 +19,13 @@ const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) => } ; +LoadMore.propTypes = { + comments: PropTypes.array.isRequired, + loadMore: PropTypes.func.isRequired, + sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired, + tab: PropTypes.oneOf(['rejected', 'premod', 'flagged']).isRequired, + assetId: PropTypes.string, + showLoadMore: PropTypes.bool.isRequired +}; + export default LoadMore; From b94401a8cfeb93e28dbab47d1420117c61dcc938 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 9 Mar 2017 11:05:18 -0700 Subject: [PATCH 20/56] make only the title clickable --- client/coral-admin/src/containers/Dashboard/FlagWidget.js | 2 +- client/coral-admin/src/containers/Dashboard/LikeWidget.js | 2 +- client/coral-admin/src/containers/Dashboard/Widget.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index 6412df7f0..7d2ffe17a 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -27,8 +27,8 @@ const FlagWidget = (props) => {

{flagSummary ? flagSummary.actionCount : 0}

{asset.title}

-

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

+

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

); }) diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/containers/Dashboard/LikeWidget.js index d01259c5d..436fbcaf3 100644 --- a/client/coral-admin/src/containers/Dashboard/LikeWidget.js +++ b/client/coral-admin/src/containers/Dashboard/LikeWidget.js @@ -28,8 +28,8 @@ const LikeWidget = (props) => {

{likeSummary ? likeSummary.actionCount : 0}

{asset.title}

-

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

+

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

); }) diff --git a/client/coral-admin/src/containers/Dashboard/Widget.css b/client/coral-admin/src/containers/Dashboard/Widget.css index db4041dbd..124663cea 100644 --- a/client/coral-admin/src/containers/Dashboard/Widget.css +++ b/client/coral-admin/src/containers/Dashboard/Widget.css @@ -64,7 +64,7 @@ } .linkToAsset { - display: block; + display: inline-block; text-decoration: none; } From db38bcb477459fb16c8e19ab3e76b55a3e4f911f Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 9 Mar 2017 12:57:54 -0700 Subject: [PATCH 21/56] modify to light grey --- client/coral-admin/src/containers/Dashboard/Dashboard.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.css b/client/coral-admin/src/containers/Dashboard/Dashboard.css index 1bd1599b4..cdad990ba 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.css +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.css @@ -9,7 +9,7 @@ } .autoUpdate { - background-color: #ffdb87; + background-color: #d5d5d5; padding: 3px 10px 10px 10px; margin-bottom: 0; @@ -22,7 +22,7 @@ float: right; border-radius: 20px; cursor: pointer; - background-color: #e6ba52; + background-color: #c0c0c0; width: 30px; height: 30px; text-align: center; From fcc93963c44ac1703bedd32f6a9fd9a8346c7d42 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 9 Mar 2017 17:07:07 -0700 Subject: [PATCH 22/56] Added support for total comment count --- graph/loaders/comments.js | 53 ++- graph/loaders/util.js | 26 +- graph/mutators/comment.js | 30 +- graph/resolvers/asset.js | 13 +- graph/typeDefs.graphql | 3 + package.json | 2 +- services/cache.js | 152 +++++++++ yarn.lock | 679 +++++++++++++++++++------------------- 8 files changed, 598 insertions(+), 360 deletions(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index afa79ea5c..e2ae29f65 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -1,4 +1,8 @@ -const util = require('./util'); +const { + SharedCounterDataLoader, + singleJoinBy, + arrayJoinBy +} = require('./util'); const DataLoader = require('dataloader'); const CommentModel = require('../../models/comment'); @@ -11,6 +15,38 @@ const CommentModel = require('../../models/comment'); * comments that we want to get */ const getCountsByAssetID = (context, asset_ids) => { + return CommentModel.aggregate([ + { + $match: { + asset_id: { + $in: asset_ids + }, + status: { + $in: ['NONE', 'ACCEPTED'] + } + } + }, + { + $group: { + _id: '$asset_id', + count: { + $sum: 1 + } + } + } + ]) + .then(singleJoinBy(asset_ids, '_id')) + .then((results) => results.map((result) => result ? result.count : 0)); +}; + +/** + * Returns the comment count for all comments that are public based on their + * asset ids. + * @param {Object} context graph context + * @param {Array} asset_ids the ids of assets for which there are + * comments that we want to get + */ +const getParentCountsByAssetID = (context, asset_ids) => { return CommentModel.aggregate([ { $match: { @@ -32,7 +68,7 @@ const getCountsByAssetID = (context, asset_ids) => { } } ]) - .then(util.singleJoinBy(asset_ids, '_id')) + .then(singleJoinBy(asset_ids, '_id')) .then((results) => results.map((result) => result ? result.count : 0)); }; @@ -64,7 +100,7 @@ const getCountsByParentID = (context, parent_ids) => { } } ]) - .then(util.singleJoinBy(parent_ids, '_id')) + .then(singleJoinBy(parent_ids, '_id')) .then((results) => results.map((result) => result ? result.count : 0)); }; @@ -216,7 +252,7 @@ const genRecentReplies = (context, ids) => { ]) .then((replies) => replies.map((reply) => reply.replies)) - .then(util.arrayJoinBy(ids, 'parent_id')); + .then(arrayJoinBy(ids, 'parent_id')); }; /** @@ -267,7 +303,7 @@ const genRecentComments = (_, ids) => { ]) .then((replies) => replies.map((reply) => reply.comments)) - .then(util.arrayJoinBy(ids, 'asset_id')); + .then(arrayJoinBy(ids, 'asset_id')); }; /** @@ -294,7 +330,7 @@ const genComments = ({user}, ids) => { } }); } - return comments.then(util.singleJoinBy(ids, 'id')); + return comments.then(singleJoinBy(ids, 'id')); }; /** @@ -307,8 +343,9 @@ module.exports = (context) => ({ get: new DataLoader((ids) => genComments(context, ids)), getByQuery: (query) => getCommentsByQuery(context, query), getCountByQuery: (query) => getCommentCountByQuery(context, query), - countByAssetID: new util.SharedCacheDataLoader('Comments.countByAssetID', 3600, (ids) => getCountsByAssetID(context, ids)), - countByParentID: new util.SharedCacheDataLoader('Comments.countByParentID', 3600, (ids) => getCountsByParentID(context, ids)), + countByAssetID: new SharedCounterDataLoader('Comments.totalCommentCount', 3600, (ids) => getCountsByAssetID(context, ids)), + parentCountByAssetID: new SharedCounterDataLoader('Comments.countByAssetID', 3600, (ids) => getParentCountsByAssetID(context, ids)), + countByParentID: new SharedCounterDataLoader('Comments.countByParentID', 3600, (ids) => getCountsByParentID(context, ids)), genRecentReplies: new DataLoader((ids) => genRecentReplies(context, ids)), genRecentComments: new DataLoader((ids) => genRecentComments(context, ids)) } diff --git a/graph/loaders/util.js b/graph/loaders/util.js index ab6f8a1f3..fe2a4e3d0 100644 --- a/graph/loaders/util.js +++ b/graph/loaders/util.js @@ -121,6 +121,29 @@ class SharedCacheDataLoader extends DataLoader { } } +/** + * SharedCounterDataLoader is identical to SharedCacheDataLoader with the + * exception in that it is designed to work with numerical cached data. + */ +class SharedCounterDataLoader extends SharedCacheDataLoader { + + /** + * Increments the key in the cache if it already exists in the cache, if not + * it does nothing. + */ + incr(key) { + return cache.incr(key, this._expiry, this._keyFunc); + } + + /** + * Decrements the key in the cache if it already exists in the cache, if not + * it does nothing. + */ + decr(key) { + return cache.decr(key, this._expiry, this._keyFunc); + } +} + /** * Maps an object's paths to a string that can be used as a cache key. * @param {Array} paths paths on the object to be used to generate the cache @@ -145,5 +168,6 @@ module.exports = { objectCacheKeyFn, arrayCacheKeyFn, SingletonResolver, - SharedCacheDataLoader + SharedCacheDataLoader, + SharedCounterDataLoader }; diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index bcab347d7..728603ae2 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -33,16 +33,17 @@ const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = }) .then((comment) => { - // TODO: explore using an `INCR` operation to update the counts here - // If the loaders are present, clear the caches for these values because we - // just added a new comment, hence the counts should be updated. - if (Comments && Comments.countByAssetID && Comments.countByParentID) { + // just added a new comment, hence the counts should be updated. We should + // perform these increments in the event that we do have a new comment that + // is approved or without a comment. + if (status === 'NONE' || status === 'APPROVED') { if (parent_id != null) { - Comments.countByParentID.clear(parent_id); + Comments.countByParentID.incr(parent_id); } else { - Comments.countByAssetID.clear(asset_id); + Comments.parentCountByAssetID.incr(asset_id); } + Comments.countByAssetID.incr(asset_id); } return comment; @@ -182,15 +183,18 @@ const setCommentStatus = ({loaders: {Comments}}, {id, status}) => { .then((comment) => { // If the loaders are present, clear the caches for these values because we - // just added a new comment, hence the counts should be updated. - if (Comments && Comments.countByAssetID && Comments.countByParentID) { - if (comment.parent_id != null) { - Comments.countByParentID.clear(comment.parent_id); - } else { - Comments.countByAssetID.clear(comment.asset_id); - } + // just added a new comment, hence the counts should be updated. It would + // be nice if we could decrement the counters here, but that would result + // in us having to know the initial state of the comment, which would + // require another database query. + if (comment.parent_id != null) { + Comments.countByParentID.clear(comment.parent_id); + } else { + Comments.parentCountByAssetID.clear(comment.asset_id); } + Comments.countByAssetID.clear(comment.asset_id); + return comment; }); }; diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index 2077b9e7a..c2a0a2b66 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -10,7 +10,18 @@ const Asset = { parent_id: null }); }, - commentCount({id}, _, {loaders: {Comments}}) { + commentCount({id, commentCount}, _, {loaders: {Comments}}) { + if (commentCount != null) { + return commentCount; + } + + return Comments.parentCountByAssetID.load(id); + }, + totalCommentCount({id, totalCommentCount}, _, {loaders: {Comments}}) { + if (totalCommentCount != null) { + return totalCommentCount; + } + return Comments.countByAssetID.load(id); }, settings({settings = null}, _, {loaders: {Settings}}) { diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 29000c84d..9655354fb 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -422,6 +422,9 @@ type Asset { # The count of top level comments on the asset. commentCount: Int + # The total count of all comments made on the asset. + totalCommentCount: Int + # The settings (rectified with the global settings) that should be applied to # this asset. settings: Settings! diff --git a/package.json b/package.json index 92442efd2..373e2adde 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "passport-facebook": "^2.1.1", "passport-local": "^1.0.0", "react-apollo": "^0.10.0", - "redis": "^2.6.3", + "redis": "^2.6.5", "uuid": "^2.0.3" }, "devDependencies": { diff --git a/services/cache.js b/services/cache.js index 21ae9911e..92da6df0a 100644 --- a/services/cache.js +++ b/services/cache.js @@ -1,5 +1,6 @@ const redis = require('./redis'); const debug = require('debug')('talk:cache'); +const crypto = require('crypto'); const cache = module.exports = { client: redis.createClient() @@ -60,6 +61,157 @@ cache.wrap = (key, expiry, work, kf = keyfunc) => { }); }; +// This is designed to increment a key and add an expiry iff the key already +// exists. +const INCR_SCRIPT = ` +if redis.call('GET', KEYS[1]) ~= false then + redis.call('INCR', KEYS[1]) + redis.call('EXPIRE', KEYS[1], ARGV[1]) +end +`; + +// Stores the SHA1 hash of INCR_SCRIPT, used for executing via EVALSHA. +let INCR_SCRIPT_HASH; + +// This is designed to decrement a key and add an expiry iff the key already +// exists. +const DECR_SCRIPT = ` +if redis.call('GET', KEYS[1]) ~= false then + redis.call('DECR', KEYS[1]) + redis.call('EXPIRE', KEYS[1], ARGV[1]) +end +`; + +// Stores the SHA1 hash of DECR_SCRIPT, used for executing via EVALSHA. +let DECR_SCRIPT_HASH; + +// Load the script into redis and track the script hash that we will use to exec +// increments on. +const loadScript = (name, script) => new Promise((resolve, reject) => { + + let shasum = crypto.createHash('sha1'); + shasum.update(script); + + let hash = shasum.digest('hex'); + + cache.client + .script('EXISTS', hash, (err, [exists]) => { + if (err) { + return reject(err); + } + + if (exists) { + debug(`already loaded ${name} as SHA[${hash}], not loading again`); + + return resolve(hash); + } + + debug(`${name} not loaded as SHA[${hash}], loading`); + + cache.client + .script('load', script, (err, hash) => { + if (err) { + return reject(err); + } + + debug(`loaded ${name} as SHA[${hash}]`); + + resolve(hash); + }); + }); +}); + +// Load the INCR_SCRIPT and DECR_SCRIPT into Redis. +Promise.all([ + loadScript('INCR_SCRIPT', INCR_SCRIPT), + loadScript('DECR_SCRIPT', DECR_SCRIPT) +]) +.then(([incrScriptHash, decrScriptHash]) => { + INCR_SCRIPT_HASH = incrScriptHash; + DECR_SCRIPT_HASH = decrScriptHash; +}) +.catch((err) => { + throw err; +}); + +/** + * This will increment a key in redis and update the expiry iff it already + * exists, otherwise it will do nothing. + */ +cache.incr = (key, expiry, kf = keyfunc) => new Promise((resolve, reject) => { + cache.client + .evalsha(INCR_SCRIPT_HASH, 1, kf(key), expiry, (err) => { + if (err) { + return reject(err); + } + + return resolve(); + }); +}); + +/** + * This will decrement a key in redis and update the expiry iff it already + * exists, otherwise it will do nothing. + */ +cache.decr = (key, expiry, kf = keyfunc) => new Promise((resolve, reject) => { + cache.client + .evalsha(DECR_SCRIPT_HASH, 1, kf(key), expiry, (err) => { + if (err) { + return reject(err); + } + + return resolve(); + }); +}); + +/** + * This will increment many keys in redis and update the expiry iff it already + * exists, otherwise it will do nothing. + */ +cache.incrMany = (keys, expiry, kf = keyfunc) => { + let multi = cache.client.multi(); + + keys.forEach((key) => { + + // Queue up the evalsha command. + multi.evalsha(INCR_SCRIPT_HASH, 1, kf(key), expiry); + }); + + return new Promise((resolve, reject) => { + multi.exec((err) => { + if (err) { + return reject(err); + } + + resolve(); + }); + }); +}; + +/** + * This will decrement many keys in redis and update the expiry iff it already + * exists, otherwise it will do nothing. + */ +cache.decrMany = (keys, expiry, kf = keyfunc) => { + let multi = cache.client.multi(); + + keys.forEach((key) => { + + // Queue up the evalsha command. + multi.evalsha(DECR_SCRIPT_HASH, 1, kf(key), expiry); + }); + + return new Promise((resolve, reject) => { + multi.exec((err) => { + if (err) { + return reject(err); + } + + resolve(); + }); + }); +}; + /** * [wrapMany description] * @param {Array} keys Either an array of objects represening diff --git a/yarn.lock b/yarn.lock index 82b5e92f3..32f0a4c2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,5 +1,7 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 + + "@kadira/storybook-deployer@^1.1.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@kadira/storybook-deployer/-/storybook-deployer-1.2.0.tgz#1708f5cb37fa08ab4173b1bd99df6f4717dfae12" @@ -289,14 +291,14 @@ assert@^1.1.1: dependencies: util "0.10.3" -assertion-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" - assertion-error@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.0.tgz#c7f85438fdd466bc7ca16ab90c81513797a5d23b" +assertion-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + ast-traverse@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ast-traverse/-/ast-traverse-0.1.1.tgz#69cf2b8386f19dcda1bb1e05d68fe359d8897de6" @@ -313,11 +315,15 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" -async@^1.4.0, async@^1.4.2, async@^1.5.2, async@1.x: +async@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0" + +async@1.x, async@^1.4.0, async@^1.4.2, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.2, async@^2.1.4, async@2.1.4: +async@2.1.4, async@^2.1.2, async@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" dependencies: @@ -331,10 +337,6 @@ async@~0.9.0: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" -async@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-1.2.1.tgz#a4816a17cd5ff516dfa2c7698a453369b9790de0" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1053,6 +1055,10 @@ babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: version "6.15.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e" +balanced-match@0.1.0, balanced-match@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a" + balanced-match@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.2.1.tgz#7bc658b4bed61eee424ad74f75f5c3e2c4df3cc7" @@ -1061,10 +1067,6 @@ balanced-match@^0.4.1, balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" -balanced-match@~0.1.0, balanced-match@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a" - base64-js@^1.0.2: version "1.2.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1" @@ -1073,7 +1075,7 @@ base64-url@1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/base64-url/-/base64-url-1.3.3.tgz#f8b6c537f09a4fc58c99cb86e0b0e9c61461a20f" -base64url@^2.0.0, base64url@2.0.0: +base64url@2.0.0, base64url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" @@ -1119,10 +1121,6 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^2.10.2: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" - bluebird@2.10.2: version "2.10.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.10.2.tgz#024a5517295308857f14f91f1106fc3b555f446b" @@ -1139,6 +1137,10 @@ bluebird@3.4.6: version "3.4.6" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.6.tgz#01da8d821d87813d158967e743d5fe6c62cf8c0f" +bluebird@^2.10.2: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" @@ -1383,7 +1385,7 @@ chai@^3.5.0: deep-eql "^0.1.3" type-detect "^1.0.0" -chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3, chalk@1.1.3: +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -1542,7 +1544,7 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-table@^0.3.1, cli-table@0.3.1: +cli-table@0.3.1, cli-table@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" dependencies: @@ -1626,18 +1628,18 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@~0.6.0-1: - version "0.6.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" - -colors@~1.1.2, colors@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" +colors@1.1.2, colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + +colors@~0.6.0-1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -1650,16 +1652,6 @@ combined-stream@~0.0.4: dependencies: delayed-stream "0.0.5" -commander@^2.5.0, commander@^2.9.0, commander@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - dependencies: - graceful-readlink ">= 1.0.0" - -commander@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781" - commander@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" @@ -1678,6 +1670,16 @@ commander@2.8.x: dependencies: graceful-readlink ">= 1.0.0" +commander@2.9.0, commander@^2.5.0, commander@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +commander@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781" + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -1805,7 +1807,7 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" -cookiejar@^2.0.6, cookiejar@2.0.x: +cookiejar@2.0.x, cookiejar@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.0.6.tgz#0abf356ad00d1c5a219d88d44518046dd026acfe" @@ -1830,7 +1832,7 @@ core-js@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" -core-util-is@~1.0.0, core-util-is@1.0.2: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2020,7 +2022,7 @@ csso@~2.3.1: clap "^1.0.9" source-map "^0.5.3" -"cssom@>= 0.3.0 < 0.4.0", cssom@0.3.x: +cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0": version "0.3.2" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" @@ -2045,16 +2047,16 @@ cz-conventional-changelog@1.1.5: dependencies: word-wrap "^1.0.3" +d3-helpers@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/d3-helpers/-/d3-helpers-0.3.0.tgz#4b31dce4a2121a77336384574d893fbed5fb293d" + d@^0.1.1, d@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" dependencies: es5-ext "~0.10.2" -d3-helpers@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/d3-helpers/-/d3-helpers-0.3.0.tgz#4b31dce4a2121a77336384574d893fbed5fb293d" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2085,17 +2087,13 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@*, debug@^2.1.1, debug@^2.2.0, debug@2, debug@2.6.0: +debug@*, debug@2, debug@2.6.0, debug@^2.1.1, debug@^2.2.0: version "2.6.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" dependencies: ms "0.7.2" -debug@^0.7.2, debug@~0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" - -debug@~2.2.0, debug@2.2.0: +debug@2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -2107,11 +2105,15 @@ debug@2.3.3: dependencies: ms "0.7.2" +debug@^0.7.2, debug@~0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" -deep-eql@^0.1.3, deep-eql@0.1.3: +deep-eql@0.1.3, deep-eql@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" dependencies: @@ -2177,14 +2179,14 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - delayed-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.5.tgz#d4b1f43a93e8296dfe02694f4680bc37a313c73f" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -2241,7 +2243,7 @@ dns-prefetch-control@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" -doctrine@^1.2.2, doctrine@1.5.0: +doctrine@1.5.0, doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" dependencies: @@ -2252,7 +2254,7 @@ doctypes@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" -dom-serializer@~0.1.0, dom-serializer@0: +dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" dependencies: @@ -2263,21 +2265,21 @@ domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" +domelementtype@1, domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + domelementtype@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" -domelementtype@~1.1.1, domelementtype@1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" - -domhandler@^2.3.0, domhandler@2.3: +domhandler@2.3, domhandler@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" dependencies: domelementtype "1" -domutils@^1.5.1, domutils@1.5, domutils@1.5.1: +domutils@1.5, domutils@1.5.1, domutils@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: @@ -2361,18 +2363,18 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" -end-of-stream@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" - dependencies: - once "~1.3.0" - end-of-stream@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e" dependencies: once "~1.3.0" +end-of-stream@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" + dependencies: + once "~1.3.0" + enhanced-resolve@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.0.3.tgz#df14c06b5fc5eecade1094c9c5a12b4b3edc0b62" @@ -2382,14 +2384,14 @@ enhanced-resolve@^3.0.0: object-assign "^4.0.1" tapable "^0.2.5" -entities@^1.1.1, entities@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" - entities@1.0: version "1.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" +entities@^1.1.1, entities@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + env-rewrite@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/env-rewrite/-/env-rewrite-1.0.2.tgz#3e344a95af1bdaab34a559479b8be3abd0804183" @@ -2463,14 +2465,14 @@ es6-map@^0.1.3: es6-symbol "~3.1.0" event-emitter "~0.3.4" -es6-promise@^3.0.2: - version "3.3.1" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" - es6-promise@3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4" +es6-promise@^3.0.2: + version "3.3.1" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" + es6-set@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8" @@ -2485,7 +2487,7 @@ es6-shim@^0.35.3: version "0.35.3" resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.3.tgz#9bfb7363feffff87a6cdb6cd93e405ec3c4b6f26" -es6-symbol@~3.1, es6-symbol@~3.1.0, es6-symbol@3: +es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" dependencies: @@ -2509,15 +2511,15 @@ escape-regexp-component@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/escape-regexp-component/-/escape-regexp-component-1.0.2.tgz#9c63b6d0b25ff2a88c3adbd18c5b61acc3b9faa2" -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5, escape-string-regexp@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - escape-string-regexp@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" -escodegen@^1.6.1, escodegen@1.x.x: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@1.x.x, escodegen@^1.6.1: version "1.8.1" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" dependencies: @@ -2653,14 +2655,14 @@ esprima-fb@~15001.1001.0-dev-harmony-fb: version "15001.1001.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659" +esprima@3.x.x, esprima@~3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + esprima@^2.6.0, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" -esprima@~3.1.0, esprima@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - esrecurse@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" @@ -2785,14 +2787,14 @@ express@^4.12.2, express@^4.14.0: utils-merge "1.0.0" vary "~1.1.0" +extend@3, extend@^3.0.0, extend@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" + extend@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/extend/-/extend-1.3.0.tgz#d1516fb0ff5624d2ebf9123ea1dac5a1994004f8" -extend@^3.0.0, extend@~3.0.0, extend@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" - external-editor@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.1.tgz#4c597c6c88fa6410e41dbbaa7b1be2336aa31095" @@ -2961,6 +2963,14 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data@1.0.0-rc4: + version "1.0.0-rc4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e" + dependencies: + async "^1.5.2" + combined-stream "^1.0.5" + mime-types "^2.1.10" + form-data@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-0.2.0.tgz#26f8bc26da6440e299cbdcfb69035c4f77a6e466" @@ -2977,14 +2987,6 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" -form-data@1.0.0-rc4: - version "1.0.0-rc4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e" - dependencies: - async "^1.5.2" - combined-stream "^1.0.5" - mime-types "^2.1.10" - formidable@^1.0.17: version "1.1.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" @@ -3199,37 +3201,6 @@ glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" -glob@^5.0.15, glob@^5.0.3: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467" @@ -3259,6 +3230,37 @@ glob@7.0.5, glob@7.0.x: once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^5.0.15, glob@^5.0.3: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + globals@^9.0.0, globals@^9.14.0: version "9.14.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034" @@ -3600,15 +3602,15 @@ https-proxy-agent@1: debug "2" extend "3" -iconv-lite@^0.4.13, iconv-lite@^0.4.5, iconv-lite@~0.4.13, iconv-lite@0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" - iconv-lite@0.4.13: version "0.4.13" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" -icss-replace-symbols@^1.0.2, icss-replace-symbols@1.0.2: +iconv-lite@0.4.15, iconv-lite@^0.4.13, iconv-lite@^0.4.5, iconv-lite@~0.4.13: + version "0.4.15" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" + +icss-replace-symbols@1.0.2, icss-replace-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5" @@ -3666,7 +3668,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@2, inherits@2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -3685,7 +3687,24 @@ inquirer-confirm@0.2.2: bluebird "2.9.24" inquirer "0.8.2" -inquirer@^0.12.0, inquirer@0.12.0: +inquirer@0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.11.1.tgz#623b6e0c101d2fe9e8e8ed902e651e0519d143e5" + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^1.0.1" + figures "^1.3.5" + lodash "^3.3.1" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + strip-ansi "^3.0.0" + through "^2.3.6" + +inquirer@0.12.0, inquirer@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" dependencies: @@ -3703,6 +3722,19 @@ inquirer@^0.12.0, inquirer@0.12.0: strip-ansi "^3.0.0" through "^2.3.6" +inquirer@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.8.2.tgz#41586548e1c5d9b3f81df7325034baacab6f58ab" + dependencies: + ansi-regex "^1.1.1" + chalk "^1.0.0" + cli-width "^1.0.1" + figures "^1.3.5" + lodash "^3.3.1" + readline2 "^0.1.1" + rx "^2.4.3" + through "^2.3.6" + inquirer@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.1.tgz#6dfbffaf4d697dd76c8fe349f919de01c28afc4d" @@ -3721,36 +3753,6 @@ inquirer@^3.0.1: strip-ansi "^3.0.0" through "^2.3.6" -inquirer@0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.11.1.tgz#623b6e0c101d2fe9e8e8ed902e651e0519d143e5" - dependencies: - ansi-escapes "^1.1.0" - ansi-regex "^2.0.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^1.0.1" - figures "^1.3.5" - lodash "^3.3.1" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - strip-ansi "^3.0.0" - through "^2.3.6" - -inquirer@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.8.2.tgz#41586548e1c5d9b3f81df7325034baacab6f58ab" - dependencies: - ansi-regex "^1.1.1" - chalk "^1.0.0" - cli-width "^1.0.1" - figures "^1.3.5" - lodash "^3.3.1" - readline2 "^0.1.1" - rx "^2.4.3" - through "^2.3.6" - interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" @@ -3998,14 +4000,14 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" -isarray@^1.0.0, isarray@~1.0.0, isarray@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + isemail@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a" @@ -4146,7 +4148,7 @@ js-tokens@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" -js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@~3.7.0, js-yaml@3.x: +js-yaml@3.x, js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" dependencies: @@ -4406,6 +4408,12 @@ linkify-it@^1.2.0: dependencies: uc.micro "^1.0.1" +linkify-it@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" + dependencies: + uc.micro "^1.0.1" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -4420,7 +4428,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@^0.2.7, loader-utils@~0.2.2, loader-utils@0.2.x: +loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@^0.2.7, loader-utils@~0.2.2: version "0.2.16" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" dependencies: @@ -4540,7 +4548,7 @@ lodash.cond@^4.3.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" -lodash.create@^3.1.1, lodash.create@3.1.1: +lodash.create@3.1.1, lodash.create@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" dependencies: @@ -4633,7 +4641,7 @@ lodash.pickby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" -lodash.reduce@^4.4.0, lodash.reduce@4.6.0: +lodash.reduce@4.6.0, lodash.reduce@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" @@ -4659,18 +4667,18 @@ lodash.words@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@^3.3.1, lodash@3.10.1: +lodash@3.10.1, lodash@^3.3.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.2, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - lodash@3.9.3: version "3.9.3" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.9.3.tgz#0159e86832feffc6d61d852b12a953b99496bd32" +lodash@^4.0.0, lodash@^4.1.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.2, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + log4js@*: version "1.1.0" resolved "https://registry.yarnpkg.com/log4js/-/log4js-1.1.0.tgz#c7d2b616d91bbf47cc65fb79d6fe04581c8096fa" @@ -4693,7 +4701,7 @@ lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" -lru-cache@^2.5.0, lru-cache@2: +lru-cache@2, lru-cache@^2.5.0: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" @@ -4774,7 +4782,7 @@ metascraper@^1.0.6: popsicle "^6.2.0" to-title-case "^1.0.0" -methods@^1.1.1, methods@^1.1.2, methods@~1.1.2, methods@1.x: +methods@1.x, methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -4823,7 +4831,7 @@ mime-types@~2.0.3: dependencies: mime-db "~1.12.0" -mime@^1.3.4, mime@1.3.4: +mime@1.3.4, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" @@ -4835,7 +4843,7 @@ minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" -minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, "minimatch@2 || 3": +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" dependencies: @@ -4848,11 +4856,7 @@ minimatch@~0.2.11, minimatch@~0.2.14: lru-cache "2" sigmund "~1.0.0" -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -minimist@~0.0.1, minimist@0.0.8: +minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -4860,11 +4864,9 @@ minimist@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.0.tgz#cdf225e8898f840a258ded44fc91776770afdc93" -mkdirp@^0.5.0, mkdirp@^0.5.1, "mkdirp@>=0.5 0", mkdirp@~0.5.0, mkdirp@~0.5.1, mkdirp@0.5.1, mkdirp@0.5.x: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" mkdirp@0.3.0: version "0.3.0" @@ -4876,6 +4878,12 @@ mkdirp@0.5.0: dependencies: minimist "0.0.8" +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + mkpath@>=0.1.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-1.0.0.tgz#ebb3a977e7af1c683ae6fda12b545a6ba6c5853d" @@ -4920,10 +4928,6 @@ mocha@^3.1.2: mkdirp "0.5.1" supports-color "3.1.2" -moment@^2.10.3, moment@2.x.x: - version "2.17.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" - moment@2.12.0: version "2.12.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.12.0.tgz#dc2560d19838d6c0731b1a6afa04675264d360d6" @@ -4932,6 +4936,10 @@ moment@2.17.0: version "2.17.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.0.tgz#a4c292e02aac5ddefb29a6eed24f51938dd3b74f" +moment@2.x.x, moment@^2.10.3: + version "2.17.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82" + mongodb-core@2.1.7: version "2.1.7" resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.7.tgz#6a27909b98142ef2508d924c274969008954fa29" @@ -4991,14 +4999,14 @@ mquery@2.2.1: regexp-clone "0.0.1" sliced "0.0.5" -ms@^0.7.1, ms@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" - ms@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" +ms@0.7.2, ms@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + muri@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/muri/-/muri-1.2.0.tgz#b86383c902920b09ebe62af0e75c94de5f33cd3d" @@ -5015,7 +5023,7 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -nan@2.5.0: +nan@2.5.0, nan@^2.3.0: version "2.5.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.0.tgz#aa8f1e34531d807e9e27755b234b4a6ec0c152a8" @@ -5212,15 +5220,15 @@ nodemon@^1.11.0: undefsafe "0.0.3" update-notifier "0.5.0" -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" +nopt@3.x, nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" dependencies: abbrev "1" -nopt@~3.0.6, nopt@3.x: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" dependencies: abbrev "1" @@ -5366,7 +5374,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -optimist@^0.6.1, optimist@>=0.3.5, optimist@0.6.1: +optimist@0.6.1, optimist@>=0.3.5, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" dependencies: @@ -5542,16 +5550,16 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" dependencies: isarray "0.0.1" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -5857,33 +5865,33 @@ postcss-mixins@^2.1.0: postcss "^5.0.10" postcss-simple-vars "^1.0.1" -postcss-modules-extract-imports@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz#8fb3fef9a6dd0420d3f6d4353cf1ff73f2b2a341" - dependencies: - postcss "^5.0.4" - postcss-modules-extract-imports@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.0.tgz#5b07f368e350cda6fd5c8844b79123a7bd3e37be" dependencies: postcss "^5.0.4" -postcss-modules-local-by-default@^1.0.1, postcss-modules-local-by-default@1.1.1: +postcss-modules-extract-imports@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz#8fb3fef9a6dd0420d3f6d4353cf1ff73f2b2a341" + dependencies: + postcss "^5.0.4" + +postcss-modules-local-by-default@1.1.1, postcss-modules-local-by-default@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.1.1.tgz#29a10673fa37d19251265ca2ba3150d9040eb4ce" dependencies: css-selector-tokenizer "^0.6.0" postcss "^5.0.4" -postcss-modules-scope@^1.0.0, postcss-modules-scope@1.0.2: +postcss-modules-scope@1.0.2, postcss-modules-scope@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.0.2.tgz#ff977395e5e06202d7362290b88b1e8cd049de29" dependencies: css-selector-tokenizer "^0.6.0" postcss "^5.0.4" -postcss-modules-values@^1.1.0, postcss-modules-values@1.2.2: +postcss-modules-values@1.2.2, postcss-modules-values@^1.1.0: version "1.2.2" resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.2.2.tgz#f0e7d476fe1ed88c5e4c7f97533a3e772ad94ca1" dependencies: @@ -6049,6 +6057,14 @@ postcss-zindex@^2.0.1: postcss "^5.0.4" uniqs "^2.0.0" +postcss@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.1.2.tgz#bd84886a66bcad489afaf7c673eed5ef639551e2" + dependencies: + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.1.2" + postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.19, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.1.2, postcss@^5.2.11, postcss@^5.2.4, postcss@^5.2.5, postcss@^5.2.9: version "5.2.11" resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.11.tgz#ff29bcd6d2efb98bfe08a022055ec599bbe7b761" @@ -6058,14 +6074,6 @@ postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0. source-map "^0.5.6" supports-color "^3.2.3" -postcss@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.1.2.tgz#bd84886a66bcad489afaf7c673eed5ef639551e2" - dependencies: - js-base64 "^2.1.9" - source-map "^0.5.6" - supports-color "^3.1.2" - pre-git@^3.10.0: version "3.12.0" resolved "https://registry.yarnpkg.com/pre-git/-/pre-git-3.12.0.tgz#8291899a15ba86f9cdaa8ca180e47096deff8dc3" @@ -6130,7 +6138,7 @@ process@^0.11.0: version "0.11.9" resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1" -progress@^1.1.8, progress@1.1.8: +progress@1.1.8, progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -6283,22 +6291,18 @@ pug@^2.0.0-beta3: pug-runtime "^2.0.3" pug-strip-comments "^1.0.2" -punycode@^1.2.4, punycode@^1.4.1, punycode@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" +punycode@1.4.1, punycode@^1.2.4, punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + pym.js@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pym.js/-/pym.js-1.1.1.tgz#3b5eb9e8499e5c95e5cb8fe29888ea7edf10a507" -q@^1.1.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" - q@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/q/-/q-1.1.2.tgz#6357e291206701d99f197ab84e57e8ad196f2a89" @@ -6311,7 +6315,15 @@ q@2.0.3: pop-iterate "^1.0.1" weak-map "^1.0.5" -qs@^6.1.0, qs@^6.2.0, qs@6.2.1: +q@^1.1.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" + +qs@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" + +qs@6.2.1, qs@^6.1.0, qs@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" @@ -6319,10 +6331,6 @@ qs@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" -qs@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" - query-string@^4.1.0, query-string@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.1.tgz#54baada6713eafc92be75c47a731f2ebd09cd11d" @@ -6342,14 +6350,14 @@ quote@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/quote/-/quote-0.4.0.tgz#10839217f6c1362b89194044d29b233fd7f32f01" -ramda@^0.22.1: - version "0.22.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.22.1.tgz#031da0c3df417c5b33c96234757eb37033f36a0e" - ramda@0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.9.1.tgz#cc914dc3a82c608d003090203787c3f6826c1d87" +ramda@^0.22.1: + version "0.22.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.22.1.tgz#031da0c3df417c5b33c96234757eb37033f36a0e" + random-bytes@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" @@ -6405,7 +6413,7 @@ react-apollo@^0.10.0: optionalDependencies: react-dom "0.14.x || 15.* || ^15.0.0" -react-dom@^15.3.1, "react-dom@0.14.x || 15.* || ^15.0.0", react-dom@15.3.2: +"react-dom@0.14.x || 15.* || ^15.0.0", react-dom@15.3.2, react-dom@^15.3.1: version "15.3.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.3.2.tgz#c46b0aa5380d7b838e7a59c4a7beff2ed315531f" @@ -6467,7 +6475,7 @@ react-tagsinput@^3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/react-tagsinput/-/react-tagsinput-3.14.0.tgz#6729f8d5b313ed8fbb35496205ec2a97654af6bc" -react@^15.3.1, react@15.3.2: +react@15.3.2, react@^15.3.1: version "15.3.2" resolved "https://registry.yarnpkg.com/react/-/react-15.3.2.tgz#a7bccd2fee8af126b0317e222c28d1d54528d09e" dependencies: @@ -6503,7 +6511,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^1.1.7, readable-stream@1.1, readable-stream@1.1.x: +readable-stream@1.1, readable-stream@1.1.x, readable-stream@^1.1.7: version "1.1.13" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" dependencies: @@ -6512,7 +6520,7 @@ readable-stream@^1.1.7, readable-stream@1.1, readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.2.2, readable-stream@2: +readable-stream@2, readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e" dependencies: @@ -6524,7 +6532,7 @@ readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2. string_decoder "~0.10.x" util-deprecate "~1.0.1" -readable-stream@^2.0.5, readable-stream@~2.1.4, readable-stream@2.1.5: +readable-stream@2.1.5, readable-stream@^2.0.5, readable-stream@~2.1.4: version "2.1.5" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" dependencies: @@ -6560,15 +6568,6 @@ readline2@^1.0.1: is-fullwidth-code-point "^1.0.0" mute-stream "0.0.5" -recast@^0.11.17: - version "0.11.20" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.20.tgz#2cb9bec269c03b36d0598118a936cd0a293ca3f3" - dependencies: - ast-types "0.9.4" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - recast@0.10.33: version "0.10.33" resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697" @@ -6578,6 +6577,15 @@ recast@0.10.33: private "~0.1.5" source-map "~0.5.0" +recast@^0.11.17: + version "0.11.20" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.20.tgz#2cb9bec269c03b36d0598118a936cd0a293ca3f3" + dependencies: + ast-types "0.9.4" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -6596,7 +6604,7 @@ redis@^0.12.1: version "0.12.1" resolved "https://registry.yarnpkg.com/redis/-/redis-0.12.1.tgz#64df76ad0fc8acebaebd2a0645e8a48fac49185e" -redis@^2.1.0, redis@^2.6.3, redis@~2.6.0-2: +redis@^2.1.0, redis@^2.6.5, redis@~2.6.0-2: version "2.6.5" resolved "https://registry.yarnpkg.com/redis/-/redis-2.6.5.tgz#87c1eff4a489f94b70871f3d08b6988f23a95687" dependencies: @@ -6741,7 +6749,7 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@^2.55.0, request@^2.74.0, request@^2.79.0, request@2.79.0: +request@2.79.0, request@^2.55.0, request@^2.74.0, request@^2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: @@ -6766,13 +6774,6 @@ request@^2.55.0, request@^2.74.0, request@^2.79.0, request@2.79.0: tunnel-agent "~0.4.1" uuid "^3.0.0" -require_optional@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.0.tgz#52a86137a849728eb60a55533617f8f914f59abf" - dependencies: - resolve-from "^2.0.0" - semver "^5.1.0" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -6796,6 +6797,13 @@ require-uncached@^1.0.2: caller-path "^0.1.0" resolve-from "^1.0.0" +require_optional@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.0.tgz#52a86137a849728eb60a55533617f8f914f59abf" + dependencies: + resolve-from "^2.0.0" + semver "^5.1.0" + resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" @@ -6832,7 +6840,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@^2.2.8, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@~2.5.1, rimraf@~2.5.4, rimraf@2: +rimraf@2, rimraf@^2.2.8, rimraf@^2.4.3, rimraf@^2.4.4, rimraf@~2.5.1, rimraf@~2.5.4: version "2.5.4" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" dependencies: @@ -6874,14 +6882,14 @@ safe-buffer@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" -sax@^1.1.4, sax@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" - sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" +sax@^1.1.4, sax@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + selenium-standalone@^5.11.2: version "5.11.2" resolved "https://registry.yarnpkg.com/selenium-standalone/-/selenium-standalone-5.11.2.tgz#724ccaa72fb26f3711e0e20989e478c4133df844" @@ -6908,18 +6916,18 @@ semver-regex@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" -semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@~5.3.0, "semver@2 || 3 || 4 || 5": +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" -semver@~5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" - semver@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.0.tgz#85f2cf8550465c4df000cf7d86f6b054106ab9e5" +semver@~5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" + send@0.14.2: version "0.14.2" resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef" @@ -6969,6 +6977,10 @@ sha.js@^2.3.6: dependencies: inherits "^2.0.1" +shelljs@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.0.tgz#ce1ed837b4b0e55b5ec3dab84251ab9dbdc0c7ec" + shelljs@^0.7.0, shelljs@^0.7.5: version "0.7.6" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.6.tgz#379cccfb56b91c8601e4793356eb5382924de9ad" @@ -6977,10 +6989,6 @@ shelljs@^0.7.0, shelljs@^0.7.5: interpret "^1.0.0" rechoir "^0.6.2" -shelljs@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.0.tgz#ce1ed837b4b0e55b5ec3dab84251ab9dbdc0c7ec" - sigmund@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" @@ -7055,7 +7063,7 @@ socks-proxy-agent@2: extend "3" socks "~1.1.5" -socks@~1.1.5, socks@1.1.9: +socks@1.1.9, socks@~1.1.5: version "1.1.9" resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.9.tgz#628d7e4d04912435445ac0b6e459376cb3e6d691" dependencies: @@ -7078,7 +7086,13 @@ source-map-support@^0.4.2: dependencies: source-map "^0.5.3" -source-map@^0.4.4, source-map@0.4.x: +source-map@0.1.x: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" + dependencies: + amdefine ">=0.0.4" + +source-map@0.4.x, source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: @@ -7094,12 +7108,6 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" -source-map@0.1.x: - version "0.1.43" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" - dependencies: - amdefine ">=0.0.4" - spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" @@ -7200,10 +7208,6 @@ strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" -string_decoder@^0.10.25, string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - string-hash@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.1.tgz#8e85bed291e0763b8f6809d9c3368fea048db3dc" @@ -7229,6 +7233,10 @@ string-width@^2.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^3.0.0" +string_decoder@^0.10.25, string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + stringmap@~0.2.2: version "0.2.2" resolved "http://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz#556c137b258f942b8776f5b2ef582aa069d7d1b1" @@ -7277,7 +7285,7 @@ style-loader@^0.13.1: dependencies: loader-utils "^0.2.7" -stylus@~0.54.5, stylus@0.54.5: +stylus@0.54.5, stylus@~0.54.5: version "0.54.5" resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.5.tgz#42b9560931ca7090ce8515a798ba9e6aa3d6dc79" dependencies: @@ -7316,6 +7324,16 @@ supertest@^2.0.1: methods "1.x" superagent "^2.0.0" +supports-color@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" + +supports-color@3.1.2, supports-color@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" + dependencies: + has-flag "^1.0.0" + supports-color@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" @@ -7324,22 +7342,12 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" - dependencies: - has-flag "^1.0.0" - supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" -supports-color@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" - svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -7427,7 +7435,7 @@ text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -through@^2.3.6, through@~2.3, through@~2.3.1, through@~2.3.8, through@2: +through@2, through@^2.3.6, through@~2.3, through@~2.3.1, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -7564,14 +7572,14 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" - type-detect@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" +type-detect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" + type-is@~1.6.14: version "1.6.14" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2" @@ -7608,7 +7616,7 @@ uid-number@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" -uid-safe@~2.1.3, uid-safe@2.1.3: +uid-safe@2.1.3, uid-safe@~2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.3.tgz#077e264a00b3187936b270bb7376a26473631071" dependencies: @@ -7645,7 +7653,7 @@ uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" -unpipe@~1.0.0, unpipe@1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -7682,7 +7690,7 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@^0.10.3, util@0.10.3: +util@0.10.3, util@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: @@ -7802,7 +7810,7 @@ whatwg-encoding@^1.0.1: dependencies: iconv-lite "0.4.13" -whatwg-fetch@^2.0.0, whatwg-fetch@>=0.10.0: +whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.2.tgz#fe294d1d89e36c5be8b3195057f2e4bc74fc980e" @@ -7827,24 +7835,28 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" -which@^1.1.1: - version "1.2.12" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" - dependencies: - isexe "^1.1.1" - which@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/which/-/which-1.1.1.tgz#9ce512459946166e12c083f08ec073380fc8cbbb" dependencies: is-absolute "^0.1.7" +which@^1.1.1: + version "1.2.12" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" + dependencies: + isexe "^1.1.1" + wide-align@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad" dependencies: string-width "^1.0.1" +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + window-size@^0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" @@ -7853,10 +7865,6 @@ window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - with@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/with/-/with-5.1.1.tgz#fa4daa92daf32c4ea94ed453c81f04686b575dfe" @@ -7864,10 +7872,14 @@ with@^5.0.0: acorn "^3.1.0" acorn-globals "^3.0.0" -word-wrap@^1.0.3, word-wrap@1.1.0: +word-wrap@1.1.0, word-wrap@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.1.0.tgz#356153d61d10610d600785c5d701288e0ae764a6" +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + wordwrap@^1.0.0, wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -7876,10 +7888,6 @@ wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -8037,4 +8045,3 @@ yauzl@^2.5.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" - From e5c108f8fb3016286d332898360f41270be9428b Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 10 Mar 2017 13:54:33 -0700 Subject: [PATCH 23/56] make notifications wider --- client/coral-embed/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index db4cbf37e..21b159e8c 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -13,7 +13,7 @@ const snackbarStyles = { color: '#fff', borderRadius: '3px 3px 0 0', textAlign: 'center', - maxWidth: '300px', + maxWidth: '400px', left: '50%', opacity: 0, transform: 'translate(-50%, 20px)', From 894395bc65fd7a9cc726d40ad4f0b69f0c33dd53 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 13 Mar 2017 09:43:47 -0600 Subject: [PATCH 24/56] Resolved #357 --- Dockerfile | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index fc0b3aca2..810d2a71f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:7.6 +FROM node:7 # Create app directory RUN mkdir -p /usr/src/app diff --git a/package.json b/package.json index 373e2adde..72b2e9b19 100644 --- a/package.json +++ b/package.json @@ -167,6 +167,6 @@ "webpack": "^2.2.1" }, "engines": { - "node": "~7.6.0" + "node": "^7.7.0" } } From 258fd8b25aebafe1a55cb0be229915cdc272b123 Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 20 Mar 2017 07:33:34 -0700 Subject: [PATCH 25/56] Initial implementation at plugins --- .gitignore | 4 ++ Dockerfile | 7 ++- graph/hooks.js | 115 +++++++++++++++++++++++++++++++++++++++ graph/loaders/index.js | 31 ++++++++--- graph/mutators/index.js | 30 ++++++++-- graph/resolvers/index.js | 21 ++++++- graph/schema.js | 12 +++- graph/typeDefs.js | 22 +++++++- package.json | 1 + plugins.js | 81 +++++++++++++++++++++++++++ yarn.lock | 33 ++++++++++- 11 files changed, 334 insertions(+), 23 deletions(-) create mode 100644 graph/hooks.js create mode 100644 plugins.js diff --git a/.gitignore b/.gitignore index fbc3c54d3..d91635c94 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,7 @@ dump.rdb coverage/ .tags .tags1 + +# remove plugin folders +plugins +plugins.json diff --git a/Dockerfile b/Dockerfile index 810d2a71f..eb3cc9c60 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,9 +12,12 @@ EXPOSE 5000 # Install app dependencies COPY package.json yarn.lock /usr/src/app/ -RUN yarn install --production +RUN yarn install # Bundle app source COPY . /usr/src/app -CMD [ "yarn", "start" ] +# Build static assets +RUN yarn build + +CMD ["yarn", "start"] diff --git a/graph/hooks.js b/graph/hooks.js new file mode 100644 index 000000000..152e02b9b --- /dev/null +++ b/graph/hooks.js @@ -0,0 +1,115 @@ +const {forEachField} = require('graphql-tools'); +const debug = require('debug')('talk:graph:schema'); + +/** + * XXX taken from graphql-js: src/execution/execute.js, because that function + * is not exported + * + * If a resolve function is not given, then a default resolve behavior is used + * which takes the property of the source object of the same name as the field + * and returns it as the result, or if it's a function, returns the result + * of calling that function. + */ +const defaultResolveFn = (source, args, context, {fieldName}) => { + + // ensure source is a value for which property access is acceptable. + if (typeof source === 'object' || typeof source === 'function') { + const property = source[fieldName]; + if (typeof property === 'function') { + return source[fieldName](args, context); + } + return property; + } +}; + +/** + * Decorates the schema with before and after hooks as provided by the Plugin + * Manager. + * @param {GraphQLSchema} schema the schema to decorate + * @param {Array} hooks hooks to apply to the schema + * @return {void} + */ +const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeName, fieldName) => { + + // Pull out the before/after hooks from the available hooks. + const { + before, + after + } = hooks + + // Only grab hooks that are associated with thie field and typeName. + .filter(({hooks}) => (typeName in hooks) && (fieldName in hooks[typeName])) + + // Grab the hooks we need. + .map(({plugin, hooks}) => ({plugin, hooks: hooks[typeName][fieldName]})) + + // Combine the before/after hooks from each plugin into an array we can + // execute. + .reduce((acc, {plugin, hooks}) => { + + // Itterate over the hooks on the fields and look at it with a switch + // block to check for misconfigured plugins. + Object.keys(hooks).forEach((hook) => { + switch (hook) { + case 'before': + debug(`adding before hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); + + if (typeof hooks.before !== 'function') { + throw new Error(`expected ${hook} hook on resolver ${typeName}.${fieldName} from plugin '${plugin.name}' to be a function, it was a '${typeof hooks[hook]}'`); + } + + acc.before.push(hooks.before); + break; + case 'after': + debug(`adding after hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); + + if (typeof hooks.after !== 'function') { + throw new Error(`expected ${hook} hook on resolver ${typeName}.${fieldName} from plugin '${plugin.name}' to be a function, it was a '${typeof hooks[hook]}'`); + } + + acc.after.unshift(hooks.after); + break; + default: + throw new Error(`invalid hook '${hook}' on resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); + } + }); + + return acc; + }, { + before: [], + after: [] + }); + + // If we have no hooks to add here, don't try to modify anything. + if (before.length === 0 && after.length === 0) { + return; + } + + // Cache the original resolve function, this emulates the beheviour found in + // graphql-tools: https://github.com/apollographql/graphql-tools/blob/6e9cc124b10d673448386041e6c3d058bc205a02/src/schemaGenerator.ts#L423-L425 + let resolve = field.resolve; + if (typeof resolve === 'undefined') { + resolve = defaultResolveFn; + } + + // Apply our async resolve function which will fire all before functions (and + // wait until they resolve) followed by waiting for the response and then + // firing their after hooks. Lastly, we respond with the result of the + // original resolver. + field.resolve = async (obj, args, context, info) => { + + // Issue all before hooks before we resolve the field. + await Promise.all(before.map(async (before) => await before(obj, args, context, info))); + + // Resolve the field. + let result = await resolve(obj, args, context, info); + + // Insure all after hooks after we've resolved the field with the result + // passed in as the fifth argument. + return await after.reduce(async (result, after) => await after(obj, args, context, info, result), result); + }; +}); + +module.exports = { + decorateWithHooks +}; diff --git a/graph/loaders/index.js b/graph/loaders/index.js index 5b1894b65..40e723918 100644 --- a/graph/loaders/index.js +++ b/graph/loaders/index.js @@ -1,4 +1,5 @@ const _ = require('lodash'); +const debug = require('debug')('talk:graph:loaders'); const Actions = require('./actions'); const Assets = require('./assets'); @@ -7,6 +8,27 @@ const Metrics = require('./metrics'); const Settings = require('./settings'); const Users = require('./users'); +const plugins = require('../../plugins'); + +let loaders = [ + + // Load the core loaders. + Actions, + Assets, + Comments, + Metrics, + Settings, + Users, + + // Load the plugin loaders from the manager. + ...plugins + .get('server', 'loaders').map(({plugin, loaders}) => { + debug(`added plugin '${plugin.name}'`); + + return loaders; + }) +]; + /** * Creates a set of loaders based on a GraphQL context. * @param {Object} context the context of the GraphQL request @@ -15,14 +37,7 @@ const Users = require('./users'); module.exports = (context) => { // We need to return an object to be accessed. - return _.merge(...[ - Actions, - Assets, - Comments, - Metrics, - Settings, - Users - ].map((loaders) => { + return _.merge(...loaders.map((loaders) => { // Each loader is a function which takes the context. return loaders(context); diff --git a/graph/mutators/index.js b/graph/mutators/index.js index b799cf83d..e3df4aefb 100644 --- a/graph/mutators/index.js +++ b/graph/mutators/index.js @@ -1,17 +1,37 @@ const _ = require('lodash'); +const debug = require('debug')('talk:graph:mutators'); const Comment = require('./comment'); const Action = require('./action'); const User = require('./user'); +const plugins = require('../../plugins'); + +let mutators = [ + + // Load in the core mutators. + Comment, + Action, + User, + + // Load the plugin mutators from the manager. + ...plugins + .get('server', 'mutators').map(({plugin, mutators}) => { + debug(`added plugin '${plugin.name}'`); + + return mutators; + }) +]; + +/** + * Creates a set of mutators based on a GraphQL context. + * @param {Object} context the context of the GraphQL request + * @return {Object} object of mutators + */ module.exports = (context) => { // We need to return an object to be accessed. - return _.merge(...[ - Comment, - Action, - User, - ].map((mutators) => { + return _.merge(...mutators.map((mutators) => { // Each set of mutators is a function which takes the context. return mutators(context); diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index 3bd8c571d..7e165f2db 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -1,3 +1,6 @@ +const _ = require('lodash'); +const debug = require('debug')('talk:graph:resolvers'); + const ActionSummary = require('./action_summary'); const Action = require('./action'); const AssetActionSummary = require('./asset_action_summary'); @@ -17,7 +20,10 @@ const UserError = require('./user_error'); const User = require('./user'); const ValidationUserError = require('./validation_user_error'); -module.exports = { +const plugins = require('../../plugins'); + +// Provide the core resolvers. +let resolvers = { ActionSummary, Action, AssetActionSummary, @@ -37,3 +43,16 @@ module.exports = { User, ValidationUserError, }; + +/** + * Plugin support requires that we merge in existing resolvers with our new + * plugin based ones. This allows plugins to extend existing resolvers as well + * as provide new ones. + */ +resolvers = plugins.get('server', 'resolvers').reduce((resolvers, {plugin}) => { + debug(`added plugin '${plugin.name}'`); + + return _.merge(resolvers, plugin.resolvers); +}, resolvers); + +module.exports = resolvers; diff --git a/graph/schema.js b/graph/schema.js index 2fca1a87f..c927d8c32 100644 --- a/graph/schema.js +++ b/graph/schema.js @@ -1,11 +1,17 @@ -const tools = require('graphql-tools'); -const maskErrors = require('graphql-errors').maskErrors; +const {makeExecutableSchema} = require('graphql-tools'); +const {maskErrors} = require('graphql-errors'); +const {decorateWithHooks} = require('./hooks'); +const plugins = require('../plugins'); const resolvers = require('./resolvers'); const typeDefs = require('./typeDefs'); -const schema = tools.makeExecutableSchema({typeDefs, resolvers}); +const schema = makeExecutableSchema({typeDefs, resolvers}); +// Plugin to the schema level resolvers to provide an before/after hook. +decorateWithHooks(schema, plugins.get('server', 'hooks')); + +// If we are in production mode, don't show server errors to the front end. if (process.env.NODE_ENV === 'production') { // Mask errors that are thrown if we are in a production environment. diff --git a/graph/typeDefs.js b/graph/typeDefs.js index 7ac982269..5b07d0f2d 100644 --- a/graph/typeDefs.js +++ b/graph/typeDefs.js @@ -4,8 +4,26 @@ const fs = require('fs'); const path = require('path'); +const {mergeStrings} = require('gql-merge'); +const debug = require('debug')('talk:graph:typeDefs'); +const plugins = require('../plugins'); -// Load the typeDefs from the graphql file. -const typeDefs = fs.readFileSync(path.join(__dirname, 'typeDefs.graphql'), 'utf8'); +/** + * Plugin support requires us to merge the type definitions from the loaded + * graphql tags, this gives us the ability to extend any portion of the + * available graph. + */ +const typeDefs = mergeStrings([ + + // Load the core graph definitions from the filesystem. + fs.readFileSync(path.join(__dirname, 'typeDefs.graphql'), 'utf8'), + + // Load the plugin definitions from the manager. + ...plugins.get('server', 'typeDefs').map(({plugin, typeDefs}) => { + debug(`added plugin '${plugin.name}'`); + + return typeDefs; + }) +]); module.exports = typeDefs; diff --git a/package.json b/package.json index 72b2e9b19..ec94e1db3 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "env-rewrite": "^1.0.2", "express": "^4.14.0", "express-session": "^1.14.2", + "gql-merge": "^0.0.4", "graphql": "^0.8.2", "graphql-errors": "^2.1.0", "graphql-server-express": "^0.5.0", diff --git a/plugins.js b/plugins.js new file mode 100644 index 000000000..7713e0b8f --- /dev/null +++ b/plugins.js @@ -0,0 +1,81 @@ +const fs = require('fs'); +const path = require('path'); + +let plugins = {}; + +// Try to parse the plugins.json file, logging out an error if the plugins.json +// file isn't loaded, but continuing. Else, like a parsing error, throw it and +// crash the program. +try { + plugins = JSON.parse(fs.readFileSync(path.join(__dirname, 'plugins.json'), 'utf8')); +} catch (err) { + if (err.code === 'ENOENT') { + console.error('plugins.json not found, plugins will not be active'); + } else { + throw err; + } +} + +/** + * Stores a reference to a section for a section of Plugins. + */ +class PluginSection { + constructor(plugin_names) { + this.plugins = plugin_names.map((plugin_name) => { + let plugin = require(`./plugins/${plugin_name}`); + + // Ensure we have a default plugin name, but allow the name to be + // overrided by the plugin. + plugin.name = plugin.name || plugin_name; + + return plugin; + }); + } + + /** + * This itterates over the section to provide all plugin hooks that are + * available. + */ + hook(hook) { + return this.plugins + .filter((plugin) => hook in plugin) + .map((plugin) => ({plugin, [hook]: plugin[hook]})); + } +} + +const NullPluginSection = new PluginSection([]); + +/** + * Stores references to all the plugins available on the application. + */ +class PluginManager { + constructor(plugins) { + this.sections = {}; + + for (let section in plugins) { + this.sections[section] = new PluginSection(plugins[section]); + } + } + + /** + * Utility function which combines the Plugins.section and PluginSection.hook + * calls. + */ + get(section, hook) { + return this.section(section).hook(hook); + } + + /** + * Returns the named section if it exists, otherwise it returns an empty + * plugin section. + */ + section(section) { + if (section in this.sections) { + return this.sections[section]; + } + + return NullPluginSection; + } +} + +module.exports = new PluginManager(plugins); diff --git a/yarn.lock b/yarn.lock index 32f0a4c2f..fdf36c0e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1133,7 +1133,7 @@ bluebird@3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.3.4.tgz#f780fe43e1a7a6510f67abd7d0d79533a40ddde6" -bluebird@3.4.6: +bluebird@3.4.6, bluebird@^3.4.6: version "3.4.6" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.6.tgz#01da8d821d87813d158967e743d5fe6c62cf8c0f" @@ -3230,7 +3230,7 @@ glob@7.0.5, glob@7.0.x: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5: +glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: @@ -3302,6 +3302,29 @@ got@^3.2.0: read-all-stream "^3.0.0" timed-out "^2.0.0" +gql-format@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/gql-format/-/gql-format-0.0.4.tgz#8237de7647de37f00aba2d0073abf6087e2da119" + dependencies: + commander "^2.9.0" + graphql "^0.7.2" + +gql-merge@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/gql-merge/-/gql-merge-0.0.4.tgz#1cb1d4cc8bb8768172cf08a45c5a4fbd0ecedc9f" + dependencies: + commander "^2.9.0" + gql-format "^0.0.4" + gql-utils "^0.0.2" + graphql "^0.7.2" + +gql-utils@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/gql-utils/-/gql-utils-0.0.2.tgz#962b3c1b34bf965a45d2564a93d3072921f61e86" + dependencies: + bluebird "^3.4.6" + glob "^7.1.1" + graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -3369,6 +3392,12 @@ graphql-tools@^0.9.0: optionalDependencies: "@types/graphql" "^0.8.5" +graphql@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.7.2.tgz#cc894a32823399b8a0cb012b9e9ecad35cd00f72" + dependencies: + iterall "1.0.2" + graphql@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.8.2.tgz#eb1bb524b38104bbf2c9157f9abc67db2feba7d2" From 7bf1510a8b50ad6121ded754c0546dbbc1eaca63 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 15 Mar 2017 09:21:24 -0600 Subject: [PATCH 26/56] Changed before/after to pre/post --- graph/hooks.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/graph/hooks.js b/graph/hooks.js index 152e02b9b..17d46ff63 100644 --- a/graph/hooks.js +++ b/graph/hooks.js @@ -23,7 +23,7 @@ const defaultResolveFn = (source, args, context, {fieldName}) => { }; /** - * Decorates the schema with before and after hooks as provided by the Plugin + * Decorates the schema with pre and post hooks as provided by the Plugin * Manager. * @param {GraphQLSchema} schema the schema to decorate * @param {Array} hooks hooks to apply to the schema @@ -31,10 +31,10 @@ const defaultResolveFn = (source, args, context, {fieldName}) => { */ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeName, fieldName) => { - // Pull out the before/after hooks from the available hooks. + // Pull out the pre/post hooks from the available hooks. const { - before, - after + pre, + post } = hooks // Only grab hooks that are associated with thie field and typeName. @@ -43,7 +43,7 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa // Grab the hooks we need. .map(({plugin, hooks}) => ({plugin, hooks: hooks[typeName][fieldName]})) - // Combine the before/after hooks from each plugin into an array we can + // Combine the pre/post hooks from each plugin into an array we can // execute. .reduce((acc, {plugin, hooks}) => { @@ -51,23 +51,23 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa // block to check for misconfigured plugins. Object.keys(hooks).forEach((hook) => { switch (hook) { - case 'before': - debug(`adding before hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); + case 'pre': + debug(`adding pre hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); - if (typeof hooks.before !== 'function') { + if (typeof hooks.pre !== 'function') { throw new Error(`expected ${hook} hook on resolver ${typeName}.${fieldName} from plugin '${plugin.name}' to be a function, it was a '${typeof hooks[hook]}'`); } - acc.before.push(hooks.before); + acc.pre.push(hooks.pre); break; - case 'after': - debug(`adding after hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); + case 'post': + debug(`adding post hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); - if (typeof hooks.after !== 'function') { + if (typeof hooks.post !== 'function') { throw new Error(`expected ${hook} hook on resolver ${typeName}.${fieldName} from plugin '${plugin.name}' to be a function, it was a '${typeof hooks[hook]}'`); } - acc.after.unshift(hooks.after); + acc.post.unshift(hooks.post); break; default: throw new Error(`invalid hook '${hook}' on resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); @@ -76,12 +76,12 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa return acc; }, { - before: [], - after: [] + pre: [], + post: [] }); // If we have no hooks to add here, don't try to modify anything. - if (before.length === 0 && after.length === 0) { + if (pre.length === 0 && post.length === 0) { return; } @@ -92,21 +92,21 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa resolve = defaultResolveFn; } - // Apply our async resolve function which will fire all before functions (and + // Apply our async resolve function which will fire all pre functions (and // wait until they resolve) followed by waiting for the response and then - // firing their after hooks. Lastly, we respond with the result of the + // firing their post hooks. Lastly, we respond with the result of the // original resolver. field.resolve = async (obj, args, context, info) => { - // Issue all before hooks before we resolve the field. - await Promise.all(before.map(async (before) => await before(obj, args, context, info))); + // Issue all pre hooks before we resolve the field. + await Promise.all(pre.map(async (pre) => await pre(obj, args, context, info))); // Resolve the field. let result = await resolve(obj, args, context, info); - // Insure all after hooks after we've resolved the field with the result + // Insure all post hooks after we've resolved the field with the result // passed in as the fifth argument. - return await after.reduce(async (result, after) => await after(obj, args, context, info, result), result); + return await post.reduce(async (result, post) => await post(obj, args, context, info, result), result); }; }); From 86f0ed80ea465ecade1dd7efda772db1484e9f24 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 15 Mar 2017 16:52:04 -0600 Subject: [PATCH 27/56] Added docs and added new context based plugins. --- PLUGINS.md | 261 +++++++++++++++++++++++++++++++++++++++++++++++ graph/context.js | 29 ++++++ graph/hooks.js | 23 ++++- 3 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 PLUGINS.md diff --git a/PLUGINS.md b/PLUGINS.md new file mode 100644 index 000000000..ca3d47ce7 --- /dev/null +++ b/PLUGINS.md @@ -0,0 +1,261 @@ +# Talk Plugins + +Plugins for Talk can take various forms, currently we are only supporting server +side plugins. + +## Plugin Registration: `plugins.json` + +All plugins must be registered in the root file `plugins.json`. + +The format for this file is thus: + +```js +{ + "server": [ + "people" + ] +} +``` + +Where we have a `server` key with an array of plugins that match the folder +name in the `plugins/` folder. For example, the above `plugins.json` would +require a plugin from `plugins/people`, which must provide a `index.js` file +that returns an object that matches the Plugin Specification. + +## Server Plugins + +### Specification + +Each plugin should export a single object with all hooks available on it. + +_**Note: You will have access to the whole core and other plugin's typeDefs, +context, loaders, mutators, resolvers, hooks. This is intentional, as it +encourages composing plugins to merge functionality, like a Slack plugin which +provides a Slack notify context function as well as having the loader for +comments.**_ + +The following are the hooks available: + +#### Field: `typeDefs` + +```graphql +enum COLOUR { + RED + BLUE +} + +type Person { + name: String! + colour: COLOUR! +} + +type RootMutation { + createPerson(name: String!): Person +} + +type RootQuery { + people: [Person!] +} +``` + +Thanks to [gql-merge](https://www.npmjs.com/package/gql-merge) the contents of +`typeDefs` should be a string that will be _merged_ with the existing type +definitions. `enum`'s will be appended to, types will be appended, and new types +will be added. + +#### Field: `context` + +```js +{ + Slack: (context) => ({ + notify: (message) => { + // return a promise after we're done sending notifications. + } + }) +} +``` + +Any property provided here will be added to the context parameter available +inside all resolvers, loaders, mutators, and of course, other context based +plugins. + +The top level item must accept a context for the request which it should use to +configure the context plugin before it would be mounted at `context.plugins`. +This plugin above would mount at: `context.plugins.Slack`, or, if you're using +[object destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), `{plugins: {Slack}}`. + +#### Field: `loaders` + +```js +(context) => ({ + People: { + load: () => db.people.find({user: context.user}) + } +}) +``` + +Loaders should be provided as a function which returns a map which is used in +the resolvers function. These must return a promise or a value. + +#### Field: `mutators` + +```js +(context) => ({ + People: { + create: (name) => { + return db.people.insert({user: context.user, name}); + } + } +}) +``` + +Mutators should be provided as a function which returns a map which is used in +the resolvers function. These must return a promise or a value. + +#### Field: `resolvers` + +```js +{ + Person: { + name(obj, args, context) { + return obj.name; + }, + colour(obj, args, context) { + // Bill likes the colour red, everyone else likes blue. + return obj.name === 'bill' ? 'RED' : 'BLUE'; + } + }, + RootQuery: { + people(obj, args, {loaders: {People}}) { + return People.load(); + } + }, + RootMutation: { + createPerson(obj, {name}, {mutators: {People}}) { + return People.create(name); + } + } +} +``` + +Should return a resolver map as described in the +[Apollo Docs](http://dev.apollodata.com/tools/graphql-tools/resolvers.html#Resolver-map). + +This will merge with the existing resolvers in core and from previous plugins. + +#### Field: `hooks` + +```js +{ + RootMutation: { + createPerson: { + post(obj, args, {plugins: {Slack}}, person) { + if (!person) { + return person; + } + + await Slack.notify(`A new person just was created with name ${person.name}`); + + return person; + } + } + } +} +``` + +Hooks here are pretty special, for each resolver field, you can specify a +pre/post hook that will execute pre and post field resolution. + +If your post function accepts four parameters, then it can modify the field +result. It is *required* that the function resolves a promise (or returns) with +the modified value or simply the original if you didn't modify it. + +### Full Example + +Contents of `plugins.json`: + +```json +{ + "server": [ + "people" + ] +} +``` + +Located in `plugins/people/index.js`: + +```js +module.exports = { + typeDefs: ` + enum COLOUR { + RED + BLUE + } + + type Person { + name: String! + colour: COLOUR! + } + + type RootQuery { + people: [Person!] + } + `, + context: { + Slack: () => ({ + notify: (message) => { + // return a promise after we're done sending notifications. + } + }) + }, + loaders: ({user}) => ({ + People: { + load: () => db.people.find({user}) + } + }), + mutators: ({user}) => ({ + People: { + create: (name) => { + return db.people.insert({user, name}); + } + } + }), + resolvers: { + Person: { + name(obj, args, context) { + return obj.name; + }, + colour(obj, args, context) { + // Bill likes the colour red, everyone else likes blue. + return obj.name === 'bill' ? 'RED' : 'BLUE'; + } + }, + RootQuery: { + people(obj, args, {loaders: {People}}) { + return People.load(); + } + }, + RootMutation: { + createPerson(obj, {name}, {mutators: {People}}) { + return People.create(name); + } + } + }, + hooks: { + RootMutation: { + createPerson: { + post(obj, args, {plugins: {Slack}}, person) { + if (!person) { + return person; + } + + await Slack.notify(`A new person just was created with name ${person.name}`); + + return person; + } + } + } + } +}; + +``` diff --git a/graph/context.js b/graph/context.js index ce8f5eb83..432ab2081 100644 --- a/graph/context.js +++ b/graph/context.js @@ -1,6 +1,32 @@ const loaders = require('./loaders'); const mutators = require('./mutators'); +const plugins = require('../plugins'); +const debug = require('debug')('talk:graph:context'); + +/** + * Contains the array of plugins that provide context to the server, these top + * level functions all need the context reference. + * @type {Array} + */ +const contextPlugins = plugins.get('server', 'context').map(({plugin, context}) => { + debug(`added plugin '${plugin.name}'`); + return {context}; +}); + +/** + * This should itterate over the passed in plugins and load them all with the + * current graph context. + * @return {Object} the saturated plugins object + */ +const decorateContextPlugins = (context, contextPlugins) => contextPlugins.reduce((acc, plugin) => { + Object.keys(plugin.context).forEach((service) => { + acc[service] = plugin.context[service](context); + }); + + return acc; +}, {}); + /** * Stores the request context. */ @@ -17,6 +43,9 @@ class Context { // Create the mutators. this.mutators = mutators(this); + + // Decorate the plugin context. + this.plugins = decorateContextPlugins(this, contextPlugins); } } diff --git a/graph/hooks.js b/graph/hooks.js index 17d46ff63..3fe1306cf 100644 --- a/graph/hooks.js +++ b/graph/hooks.js @@ -99,14 +99,31 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa field.resolve = async (obj, args, context, info) => { // Issue all pre hooks before we resolve the field. - await Promise.all(pre.map(async (pre) => await pre(obj, args, context, info))); + await Promise.all(pre.map((pre) => pre(obj, args, context, info))); // Resolve the field. - let result = await resolve(obj, args, context, info); + let result = resolve(obj, args, context, info); // Insure all post hooks after we've resolved the field with the result // passed in as the fifth argument. - return await post.reduce(async (result, post) => await post(obj, args, context, info, result), result); + return await post.reduce(async (result, post) => { + + // Wait for the accumulator to resolve before we continue. + result = await result; + + // Check to see if this post function accepts a result, if it does, we + // expect that it modifies the result, otherwise, just fire the post hook, + // wait till it's done, then move onto the next hook. + if (post.length === 5) { + return await post(obj, args, context, info, result); + } + + // Wait for the post hook to finish. + await post(obj, args, context, info); + + // Return the result, which we already awaited for before. + return result; + }, result); }; }); From 32af28bfdc425be6d3ce0de841d8fa135884baa5 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 15 Mar 2017 16:54:07 -0600 Subject: [PATCH 28/56] Fixed typo --- PLUGINS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PLUGINS.md b/PLUGINS.md index ca3d47ce7..1f8569029 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -197,6 +197,10 @@ module.exports = { colour: COLOUR! } + type RootMutation { + createPerson(name: String!): Person + } + type RootQuery { people: [Person!] } From a7c946f9af800b550fe2f87b544e6dd08ccdb865 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 16 Mar 2017 12:31:25 -0600 Subject: [PATCH 29/56] Fixed bug in docs --- PLUGINS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 1f8569029..eeb033d5a 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -149,7 +149,7 @@ This will merge with the existing resolvers in core and from previous plugins. { RootMutation: { createPerson: { - post(obj, args, {plugins: {Slack}}, person) { + post: async (obj, args, {plugins: {Slack}}, person) { if (!person) { return person; } @@ -248,7 +248,7 @@ module.exports = { hooks: { RootMutation: { createPerson: { - post(obj, args, {plugins: {Slack}}, person) { + post: async (obj, args, {plugins: {Slack}}, person) { if (!person) { return person; } From ae4bae6cd2baae18737038d7eccf903a44c60d8c Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 9 Mar 2017 18:06:44 -0500 Subject: [PATCH 30/56] Scrolling to keep selected comments in view. --- .../containers/ModerationQueue/ModerationContainer.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index e53162992..70fe0cdd5 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -3,6 +3,7 @@ import {connect} from 'react-redux'; import {compose} from 'react-apollo'; import key from 'keymaster'; import isEqual from 'lodash/isEqual'; +import styles from './components/styles.css'; import {modQueueQuery} from '../../graphql/queries'; import {banUser, setCommentStatus} from '../../graphql/mutations'; @@ -90,6 +91,15 @@ class ModerationContainer extends Component { key.unbind('t'); } + componentDidUpdate(_, prevState) { + + // If paging through using keybaord shortcuts, scroll the page to keep the selected + // comment in view. + if (prevState.selectedIndex !== this.state.selectedIndex) { + document.querySelector(`.${styles.selected}`).scrollIntoViewIfNeeded(); + } + } + componentWillReceiveProps(nextProps) { const {updateAssets} = this.props; if(!isEqual(nextProps.data.assets, this.props.data.assets)) { From 28b24ebb09854d50fea0b4b1357163c937cd427a Mon Sep 17 00:00:00 2001 From: David Erwin Date: Thu, 16 Mar 2017 14:32:59 -0400 Subject: [PATCH 31/56] Fix bug in example --- PLUGINS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLUGINS.md b/PLUGINS.md index eeb033d5a..20ba789be 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -248,7 +248,7 @@ module.exports = { hooks: { RootMutation: { createPerson: { - post: async (obj, args, {plugins: {Slack}}, person) { + post: async (obj, args, {plugins: {Slack}}, person) => { if (!person) { return person; } From 46e8d30e22af4e27adb978bff7fb08d321225b25 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 13 Mar 2017 11:13:28 -0600 Subject: [PATCH 32/56] scrollIntoView will work in more browsers --- .../src/containers/ModerationQueue/ModerationContainer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 70fe0cdd5..e254a75d6 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -96,7 +96,9 @@ class ModerationContainer extends Component { // If paging through using keybaord shortcuts, scroll the page to keep the selected // comment in view. if (prevState.selectedIndex !== this.state.selectedIndex) { - document.querySelector(`.${styles.selected}`).scrollIntoViewIfNeeded(); + + // the 'smooth' flag only works in FF as of March 2017 + document.querySelector(`.${styles.selected}`).scrollIntoView({behavior: 'smooth'}); } } From c8a0ded4adae656afa1ed6d32406492ec7a5a951 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 14 Mar 2017 13:08:51 -0600 Subject: [PATCH 33/56] try to just add recaptcha manually use react-recaptcha remove recaptcha file --- client/coral-admin/src/actions/auth.js | 15 ++++++++++--- .../coral-admin/src/components/AdminLogin.js | 22 ++++++++++++++++++- client/coral-admin/src/constants/auth.js | 1 + .../src/containers/LayoutContainer.js | 4 +++- client/coral-admin/src/reducers/auth.js | 7 ++++++ package.json | 1 + views/admin.ejs | 1 + webpack.config.js | 3 +++ yarn.lock | 4 ++++ 9 files changed, 53 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 389e8b4d2..3e6d81ae1 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -2,15 +2,24 @@ import * as actions from '../constants/auth'; import coralApi from 'coral-framework/helpers/response'; // Log In. -export const handleLogin = (email, password) => dispatch => { +export const handleLogin = (email, password, recaptchaResponse) => dispatch => { dispatch({type: actions.LOGIN_REQUEST}); - return coralApi('/auth/local', {method: 'POST', body: {email, password}}) + const params = {method: 'POST', body: {email, password}}; + if (recaptchaResponse) { + params.headers = {'X-Recaptcha-Response': recaptchaResponse}; + } + return coralApi('/auth/local', params) .then(result => { const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length; dispatch(checkLoginSuccess(result.user, isAdmin)); }) .catch(error => { - dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key}); + + if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { + dispatch({type: actions.LOGIN_MAXIMUM_EXCEEDED, message: error.translation_key}); + } else { + dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key}); + } }); }; diff --git a/client/coral-admin/src/components/AdminLogin.js b/client/coral-admin/src/components/AdminLogin.js index 7c7d4b00f..11e840eb7 100644 --- a/client/coral-admin/src/components/AdminLogin.js +++ b/client/coral-admin/src/components/AdminLogin.js @@ -4,6 +4,7 @@ import styles from './NotFound.css'; import {Button, TextField, Alert, Success} from 'coral-ui'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; +import Recaptcha from 'react-recaptcha'; const lang = new I18n(translations); class AdminLogin extends React.Component { @@ -18,13 +19,22 @@ class AdminLogin extends React.Component { this.props.handleLogin(this.state.email, this.state.password); } + onRecaptchaLoad = () => { + + // do something? + } + + onRecaptchaVerify = (recaptchaResponse) => { + this.props.handleLogin(this.state.email, this.state.password, recaptchaResponse); + } + handleRequestPassword = e => { e.preventDefault(); this.props.requestPasswordReset(this.state.email); } render () { - const {errorMessage} = this.props; + const {errorMessage, loginMaxExceeded} = this.props; const signInForm = (
{errorMessage && {lang.t(`errors.${errorMessage}`)}} @@ -49,6 +59,15 @@ class AdminLogin extends React.Component { this.setState({requestPassword: true}); }}>Request a new one.

+ { + loginMaxExceeded && + + } ); const requestPasswordForm = ( @@ -84,6 +103,7 @@ class AdminLogin extends React.Component { } AdminLogin.propTypes = { + loginMaxExceeded: PropTypes.bool.isRequired, handleLogin: PropTypes.func.isRequired, passwordRequestSuccess: PropTypes.string, loginError: PropTypes.string diff --git a/client/coral-admin/src/constants/auth.js b/client/coral-admin/src/constants/auth.js index 3c47bfc45..93cd61544 100644 --- a/client/coral-admin/src/constants/auth.js +++ b/client/coral-admin/src/constants/auth.js @@ -11,6 +11,7 @@ export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'; export const LOGIN_REQUEST = 'LOGIN_REQUEST'; export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; export const LOGIN_FAILURE = 'LOGIN_FAILURE'; +export const LOGIN_MAXIMUM_EXCEEDED = 'LOGIN_MAXIMUM_EXCEEDED'; export const FETCH_FORGOT_PASSWORD_REQUEST = 'FETCH_FORGOT_PASSWORD_REQUEST'; export const FETCH_FORGOT_PASSWORD_SUCCESS = 'FETCH_FORGOT_PASSWORD_SUCCESS'; diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/LayoutContainer.js index d122cd23a..d2493a548 100644 --- a/client/coral-admin/src/containers/LayoutContainer.js +++ b/client/coral-admin/src/containers/LayoutContainer.js @@ -16,12 +16,14 @@ class LayoutContainer extends Component { loggedIn, loadingUser, loginError, + loginMaxExceeded, passwordRequestSuccess } = this.props.auth; const {handleLogout} = this.props; if (loadingUser) { return ; } if (!isAdmin) { return ({ const mapDispatchToProps = dispatch => ({ checkLogin: () => dispatch(checkLogin()), - handleLogin: (username, password) => dispatch(handleLogin(username, password)), + handleLogin: (username, password, recaptchaResponse) => dispatch(handleLogin(username, password, recaptchaResponse)), requestPasswordReset: email => dispatch(requestPasswordReset(email)), handleLogout: () => dispatch(logout()) }); diff --git a/client/coral-admin/src/reducers/auth.js b/client/coral-admin/src/reducers/auth.js index b52efdb85..a7054ddfa 100644 --- a/client/coral-admin/src/reducers/auth.js +++ b/client/coral-admin/src/reducers/auth.js @@ -6,6 +6,7 @@ const initialState = Map({ user: null, isAdmin: false, loginError: null, + loginMaxExceeded: false, passwordRequestSuccess: null }); @@ -29,12 +30,18 @@ export default function auth (state = initialState, action) { return initialState; case actions.LOGIN_REQUEST: return state.set('loginError', null); + case actions.LOGIN_SUCCESS: + return state.set('loginMaxExceeded', false).set('loginError', null); case actions.LOGIN_FAILURE: return state.set('loginError', action.message); case actions.FETCH_FORGOT_PASSWORD_REQUEST: return state.set('passwordRequestSuccess', null); case actions.FETCH_FORGOT_PASSWORD_SUCCESS: return state.set('passwordRequestSuccess', 'If you have a registered account, a password reset link was sent to that email.'); + case actions.LOGIN_MAXIMUM_EXCEEDED: + return state + .set('loginMaxExceeded', true) + .set('loginError', action.message); default : return state; } diff --git a/package.json b/package.json index 7da9305f8..36899a04b 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "passport-facebook": "^2.1.1", "passport-local": "^1.0.0", "react-apollo": "^0.10.0", + "react-recaptcha": "^2.2.6", "redis": "^2.6.5", "uuid": "^2.0.3" }, diff --git a/views/admin.ejs b/views/admin.ejs index dafd5e3cc..d4d635dcc 100644 --- a/views/admin.ejs +++ b/views/admin.ejs @@ -85,6 +85,7 @@
+ diff --git a/webpack.config.js b/webpack.config.js index 432bb6155..e7652a67d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -118,6 +118,9 @@ module.exports = { 'process.env': { 'VERSION': `"${require('./package.json').version}"` } + }), + new webpack.EnvironmentPlugin({ + TALK_RECAPTCHA_PUBLIC: JSON.stringify(process.env.TALK_RECAPTCHA_PUBLIC) }) ], resolve: { diff --git a/yarn.lock b/yarn.lock index a2308f16d..b5f96464e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6452,6 +6452,10 @@ react-onclickoutside@^5.7.1: version "5.8.4" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-5.8.4.tgz#a2c673a8d1b104a550e565574b95419feb12cc3f" +react-recaptcha@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.2.6.tgz#bb44c1948a39b37d5a41920c73db833e5d8524f9" + react-redux@^4.4.5: version "4.4.6" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.6.tgz#4b9d32985307a11096a2dd61561980044fcc6209" From 2c4bf52f6bee4805946e1c6b7b79d55078001c4c Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 9 Mar 2017 16:05:09 -0700 Subject: [PATCH 34/56] add Activity widget --- .../containers/Dashboard/ActivityWidget.js | 49 +++++++++++++++++++ .../src/containers/Dashboard/Dashboard.js | 4 +- .../src/containers/Dashboard/FlagWidget.js | 15 ++++-- .../src/containers/Dashboard/LikeWidget.js | 16 ++++-- .../fragments/assetMetricsView.graphql | 1 + .../src/graphql/queries/metricsQuery.graphql | 3 ++ 6 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 client/coral-admin/src/containers/Dashboard/ActivityWidget.js diff --git a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js new file mode 100644 index 000000000..e1d0efe34 --- /dev/null +++ b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js @@ -0,0 +1,49 @@ +import React, {PropTypes} from 'react'; +import {Link} from 'react-router'; +import styles from './Widget.css'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-admin/src/translations'; + +const lang = new I18n(translations); + +const ActivityWidget = ({assets}) => { + return ( +
+

Articles with the most comments (parent)

+
+

{lang.t('streams.article')}

+

{lang.t('dashboard.comment_count')}

+
+
+ { + assets.length + ? assets.map(asset => { + return ( +
+ Moderate +

{asset.commentCount}

+ +

{asset.title}

+ +

{asset.author} — Published: {new Date(asset.created_at).toLocaleDateString()}

+
+ ); + }) + :
{lang.t('dashboard.no_activity')}
+ } +
+
+ ); +}; + +ActivityWidget.propTypes = { + assets: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + url: PropTypes.string, + commentCount: PropTypes.number, + author: PropTypes.string, + created_at: PropTypes.string + })).isRequired +}; + +export default ActivityWidget; diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js index 5b4a20282..f1259f796 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.js @@ -5,6 +5,7 @@ import {connect} from 'react-redux'; import {getMetrics} from 'coral-admin/src/graphql/queries'; import FlagWidget from './FlagWidget'; import LikeWidget from './LikeWidget'; +import ActivityWidget from './ActivityWidget'; import {showBanUserDialog, hideBanUserDialog} from 'coral-admin/src/actions/moderation'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations'; @@ -47,7 +48,7 @@ class Dashboard extends React.Component { return ; } - const {data: {assetsByLike, assetsByFlag}} = this.props; + const {data: {assetsByLike, assetsByFlag, assetsByActivity}} = this.props; return (
@@ -61,6 +62,7 @@ class Dashboard extends React.Component {
+
); diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index 7d2ffe17a..468875d3d 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import {Link} from 'react-router'; import styles from './Widget.css'; import I18n from 'coral-framework/modules/i18n/i18n'; @@ -6,8 +6,7 @@ import translations from 'coral-admin/src/translations'; const lang = new I18n(translations); -const FlagWidget = (props) => { - const {assets} = props; +const FlagWidget = ({assets}) => { return (
@@ -39,4 +38,14 @@ const FlagWidget = (props) => { ); }; +FlagWidget.propTypes = { + assets: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + url: PropTypes.string, + action_summaries: PropTypes.array, + author: PropTypes.string, + created_at: PropTypes.string + })).isRequired +}; + export default FlagWidget; diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/containers/Dashboard/LikeWidget.js index 436fbcaf3..b878c281b 100644 --- a/client/coral-admin/src/containers/Dashboard/LikeWidget.js +++ b/client/coral-admin/src/containers/Dashboard/LikeWidget.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import {Link} from 'react-router'; import styles from './Widget.css'; import I18n from 'coral-framework/modules/i18n/i18n'; @@ -6,9 +6,7 @@ import translations from 'coral-admin/src/translations'; const lang = new I18n(translations); -const LikeWidget = (props) => { - - const {assets} = props; +const LikeWidget = ({assets}) => { return (
@@ -40,4 +38,14 @@ const LikeWidget = (props) => { ); }; +LikeWidget.propTypes = { + assets: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + url: PropTypes.string, + action_summaries: PropTypes.array, + author: PropTypes.string, + created_at: PropTypes.string + })).isRequired +}; + export default LikeWidget; diff --git a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql b/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql index 37335aeaa..c77fbc32b 100644 --- a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql +++ b/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql @@ -4,6 +4,7 @@ fragment metrics on Asset { url author created_at + commentCount action_summaries { type: __typename actionCount diff --git a/client/coral-admin/src/graphql/queries/metricsQuery.graphql b/client/coral-admin/src/graphql/queries/metricsQuery.graphql index 42a9fb70e..f0dff8965 100644 --- a/client/coral-admin/src/graphql/queries/metricsQuery.graphql +++ b/client/coral-admin/src/graphql/queries/metricsQuery.graphql @@ -7,4 +7,7 @@ query Metrics ($from: Date!, $to: Date!) { assetsByLike: assetMetrics(from: $from, to: $to, sort: LIKE) { ...metrics } + assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) { + ...metrics + } } From f0059078945115205432c558d06100c7372ac813 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 10 Mar 2017 12:15:47 -0700 Subject: [PATCH 35/56] add some translations --- client/coral-admin/src/containers/Dashboard/Widget.css | 1 - client/coral-admin/src/translations.json | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Dashboard/Widget.css b/client/coral-admin/src/containers/Dashboard/Widget.css index 9912ac4e4..b4515786f 100644 --- a/client/coral-admin/src/containers/Dashboard/Widget.css +++ b/client/coral-admin/src/containers/Dashboard/Widget.css @@ -23,7 +23,6 @@ padding-left: 10px; font-size: 1.5rem; font-weight: bold; - background-color: #e0e0e0; } .widgetTable { diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 7e0f13989..25c7900bc 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -136,6 +136,7 @@ "no_flags": "There have been no flags in the last 5 minutes! Hooray!", "no_likes": "There have been no likes in the last 5 minutes. All quiet.", "flags": "Flags", + "no_activity": "There haven't been any comments anywhere in the last five minutes.", "comment_count": "comments" }, "streams": { @@ -283,6 +284,7 @@ "no_flags": "¡Nadie ha marcado nada en los últimos 5 minutos! ¡Bravo!", "no_likes": "A nadie le ha gustado algún comentario en los últimos 5 minutos. Todo tranquilo.", "flags": "Marcados", + "no_activity": "No hubo comentarios en los ultimos 5 minutos", "comment_count": "comentarios" }, "streams": { From 2124ca0f34c099a51e10dfd234e97d6b697856f7 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 10 Mar 2017 13:48:14 -0700 Subject: [PATCH 36/56] save dismissed timer note to localStorage --- .../src/containers/Dashboard/Dashboard.js | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js index 5b4a20282..00af443d0 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.js @@ -15,9 +15,22 @@ const refreshIntervalSeconds = 60 * 5; class Dashboard extends React.Component { - state = { - noteHidden: false, - secondsUntilRefresh: refreshIntervalSeconds + constructor (props) { + super(props); + + try { + if (window.localStorage.getItem('coral:dashboardNote') === null) { + window.localStorage.setItem('coral:dashboardNote', 'show'); + } + } catch (e) { + + // above will fail in Private Mode in some browsers. + } + + this.state = { + secondsUntilRefresh: refreshIntervalSeconds, + dashboardNote: window.localStorage.getItem('coral:dashboardNote') || 'show' + }; } componentWillMount () { @@ -31,6 +44,16 @@ class Dashboard extends React.Component { }, 1000); } + dismissNote = () => { + try { + window.localStorage.setItem('coral:dashboardNote', 'hide'); + } catch (e) { + + // when setItem fails in Safari Private mode + this.setState({dashboardNote: 'hide'}); + } + } + formatTime = () => { const minutes = Math.floor(this.state.secondsUntilRefresh / 60); let seconds = (this.state.secondsUntilRefresh % 60).toString(); @@ -48,13 +71,15 @@ class Dashboard extends React.Component { } const {data: {assetsByLike, assetsByFlag}} = this.props; + const hideReloadNote = window.localStorage.getItem('coral:dashboardNote') === 'hide' || + this.state.dashboardNote === 'hide'; // for Safari Incognito return (

this.setState({noteHidden: true})}> + onClick={this.dismissNote}> × {lang.t('dashboard.next-update', this.formatTime())} {lang.t('dashboard.auto-update')}

From 21acb0d1d7b531f50cebe23a5d3a6972e22daec7 Mon Sep 17 00:00:00 2001 From: riley Date: Mon, 13 Mar 2017 13:23:00 -0600 Subject: [PATCH 37/56] indentation and flag -> report --- client/coral-embed-stream/src/Comment.js | 50 ++++++++++----------- client/coral-framework/translations.json | 2 +- client/coral-plugin-flags/translations.json | 8 ++-- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 0a3cdb78b..20060f474 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -157,31 +157,31 @@ class Comment extends React.Component {
- - - - - setActiveReplyBox(comment.id)} - parentCommentId={parentId || comment.id} - currentUserId={currentUser && currentUser.id} - banned={false} /> - - - - - - -
+ + + + + setActiveReplyBox(comment.id)} + parentCommentId={parentId || comment.id} + currentUserId={currentUser && currentUser.id} + banned={false} /> + + + + + + +
diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index d115aedca..ffe1711fa 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -5,7 +5,7 @@ "successNameUpdate": "Your username has been updated", "contentNotAvailable": "This content is not available", "loadMore": "View more", - "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", + "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Report, or write comments. Please contact moderator@fakeurl.com for more information", "editName": { "msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.", "label": "New Username", diff --git a/client/coral-plugin-flags/translations.json b/client/coral-plugin-flags/translations.json index caac6cbf1..255e13f3c 100644 --- a/client/coral-plugin-flags/translations.json +++ b/client/coral-plugin-flags/translations.json @@ -7,8 +7,8 @@ "step-1-header": "Report an issue", "step-2-header": "Help us understand", "step-3-header": "Thank you for your input", - "flag-username": "Flag username", - "flag-comment": "Flag comment", + "flag-username": "Report username", + "flag-comment": "Report comment", "continue": "Continue", "done": "Done", "no-agree-comment": "I don't agree with this comment", @@ -20,8 +20,8 @@ "no-like-bio": "I don't like this bio", "marketing": "This looks like an ad/marketing", "user-impersonating": "This user is impersonating", - "thank-you": "We value your safety and feedback. A moderator will review your flag.", - "flag-reason": "Reason for flag (Optional)", + "thank-you": "We value your safety and feedback. A moderator will review your report.", + "flag-reason": "Reason for reporting (Optional)", "other": "Other" }, "es": { From 39e69f3bdc57a01a316a931ee1f1fb5aafff066c Mon Sep 17 00:00:00 2001 From: riley Date: Mon, 13 Mar 2017 12:08:57 -0600 Subject: [PATCH 38/56] button text was wrapping in firefox --- client/coral-admin/src/components/ModerationList.css | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/components/ModerationList.css b/client/coral-admin/src/components/ModerationList.css index 7036998d1..fc2ba9931 100644 --- a/client/coral-admin/src/components/ModerationList.css +++ b/client/coral-admin/src/components/ModerationList.css @@ -186,4 +186,5 @@ .actionButton { transform: scale(.8); margin: 0; + width: 140px; } From 337b6accecbd35bf48211a75692b5b9a8874f92f Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 13 Mar 2017 13:33:31 -0600 Subject: [PATCH 39/56] remove fakeurl --- client/coral-framework/translations.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index ffe1711fa..d95f5c9ec 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -5,9 +5,9 @@ "successNameUpdate": "Your username has been updated", "contentNotAvailable": "This content is not available", "loadMore": "View more", - "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Report, or write comments. Please contact moderator@fakeurl.com for more information", + "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Report, or write comments. Please contact us if you have any questions.", "editName": { - "msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.", + "msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. Please contact us if you have any questions.", "label": "New Username", "button": "Submit", "error": "Usernames can contain letters, numbers and _ only" @@ -41,7 +41,7 @@ "successUpdateSettings": "La configuración de este articulo fue actualizada", "successBioUpdate": "Tu bio fue actualizada", "contentNotAvailable": "El contenido no se encuentra disponible", - "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information", + "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios.", "editNameMsg": "", "loadMore": "Ver más", "newCount": "Ver {0} {1} más", From c55b4d22d7d1bc95003aa6c5446e7e92fbff5332 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Tue, 14 Mar 2017 17:07:48 -0400 Subject: [PATCH 40/56] Language update --- client/coral-admin/src/containers/Dashboard/ActivityWidget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js index e1d0efe34..a6e293b82 100644 --- a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js +++ b/client/coral-admin/src/containers/Dashboard/ActivityWidget.js @@ -9,7 +9,7 @@ const lang = new I18n(translations); const ActivityWidget = ({assets}) => { return (
-

Articles with the most comments (parent)

+

Articles with the most conversations

{lang.t('streams.article')}

{lang.t('dashboard.comment_count')}

From abd0b5144196a7914597e2178f2e0240fce5408b Mon Sep 17 00:00:00 2001 From: David Erwin Date: Tue, 14 Mar 2017 17:10:37 -0400 Subject: [PATCH 41/56] Removing log --- graph/resolvers/root_query.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 9f49a5b07..ce11fcae9 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -63,8 +63,6 @@ const RootQuery = { return null; } - console.log({from, to, sort, limit}); - if (sort === 'ACTIVITY') { return Assets.getActivity({from, to, limit}); } From 1ff5ab2e3ee4a87d8f0865b6946cc570db140011 Mon Sep 17 00:00:00 2001 From: riley Date: Mon, 13 Mar 2017 12:15:38 -0600 Subject: [PATCH 42/56] remove like widget for now --- client/coral-admin/src/containers/Dashboard/Dashboard.js | 4 +--- client/coral-admin/src/graphql/queries/metricsQuery.graphql | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js index 5b4a20282..aca79f27d 100644 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ b/client/coral-admin/src/containers/Dashboard/Dashboard.js @@ -4,7 +4,6 @@ import {compose} from 'react-apollo'; import {connect} from 'react-redux'; import {getMetrics} from 'coral-admin/src/graphql/queries'; import FlagWidget from './FlagWidget'; -import LikeWidget from './LikeWidget'; import {showBanUserDialog, hideBanUserDialog} from 'coral-admin/src/actions/moderation'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-admin/src/translations'; @@ -47,7 +46,7 @@ class Dashboard extends React.Component { return ; } - const {data: {assetsByLike, assetsByFlag}} = this.props; + const {data: {assetsByFlag}} = this.props; return (
@@ -60,7 +59,6 @@ class Dashboard extends React.Component {

-
); diff --git a/client/coral-admin/src/graphql/queries/metricsQuery.graphql b/client/coral-admin/src/graphql/queries/metricsQuery.graphql index 42a9fb70e..39bdba0e7 100644 --- a/client/coral-admin/src/graphql/queries/metricsQuery.graphql +++ b/client/coral-admin/src/graphql/queries/metricsQuery.graphql @@ -4,7 +4,4 @@ query Metrics ($from: Date!, $to: Date!) { assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) { ...metrics } - assetsByLike: assetMetrics(from: $from, to: $to, sort: LIKE) { - ...metrics - } } From 63c3d02329e267ae01f56ca13039cf4787cb4d66 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Tue, 14 Mar 2017 16:30:02 -0600 Subject: [PATCH 43/56] change Stream > Story sometimes --- client/coral-admin/src/AppRouter.js | 4 +- .../coral-admin/src/components/ui/Drawer.js | 4 +- .../coral-admin/src/components/ui/Header.js | 4 +- .../Streams.css => Stories/Stories.css} | 0 .../Streams.js => Stories/Stories.js} | 6 +- .../src/containers/Streams/Stories.js | 187 ++++++++++++++++++ client/coral-admin/src/translations.json | 10 +- 7 files changed, 201 insertions(+), 14 deletions(-) rename client/coral-admin/src/containers/{Streams/Streams.css => Stories/Stories.css} (100%) rename client/coral-admin/src/containers/{Streams/Streams.js => Stories/Stories.js} (97%) create mode 100644 client/coral-admin/src/containers/Streams/Stories.js diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index af748ab7b..2bd59b79d 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -1,7 +1,7 @@ import React from 'react'; import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router'; -import Streams from 'containers/Streams/Streams'; +import Stories from 'containers/Stories/Stories'; import Configure from 'containers/Configure/Configure'; import LayoutContainer from 'containers/LayoutContainer'; import InstallContainer from 'containers/Install/InstallContainer'; @@ -21,7 +21,7 @@ const routes = ( - + {/* Community Routes */} diff --git a/client/coral-admin/src/components/ui/Drawer.js b/client/coral-admin/src/components/ui/Drawer.js index 9af651f3b..5ac55045c 100644 --- a/client/coral-admin/src/components/ui/Drawer.js +++ b/client/coral-admin/src/components/ui/Drawer.js @@ -23,9 +23,9 @@ const CoralDrawer = ({handleLogout, restricted = false}) => ( {lang.t('configure.moderate')} - {lang.t('configure.streams')} + {lang.t('configure.stories')} ( - {lang.t('configure.streams')} + {lang.t('configure.stories')} { }; }; -export default connect(mapStateToProps, mapDispatchToProps)(Streams); +export default connect(mapStateToProps, mapDispatchToProps)(Stories); const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Streams/Stories.js b/client/coral-admin/src/containers/Streams/Stories.js new file mode 100644 index 000000000..4d2ad086a --- /dev/null +++ b/client/coral-admin/src/containers/Streams/Stories.js @@ -0,0 +1,187 @@ +import React, {Component} from 'react'; +import styles from './Stories.css'; +import {connect} from 'react-redux'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import {fetchAssets, updateAssetState} from '../../actions/assets'; +import translations from '../../translations.json'; +import {Link} from 'react-router'; + +import {Pager, Icon} from 'coral-ui'; +import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; +import EmptyCard from 'coral-admin/src/components/EmptyCard'; + +const limit = 25; + +class Stories extends Component { + + state = { + search: '', + sort: 'desc', + filter: 'all', + statusMenus: {}, + timer: null, + page: 0 + } + + componentDidMount () { + this.props.fetchAssets(0, limit, '', this.state.sortBy); + } + + onSettingChange = (setting) => (e) => { + let options = this.state; + this.setState({[setting]: e.target.value}); + options[setting] = e.target.value; + this.props.fetchAssets(0, limit, options.search, options.sort, options.filter); + } + + onSearchChange = (e) => { + const search = e.target.value; + this.setState((prevState) => { + prevState.search = search; + clearTimeout(prevState.timer); + const fetchAssets = this.props.fetchAssets; + prevState.timer = setTimeout(() => { + fetchAssets(0, limit, search, this.state.sort, this.state.filter); + }, 350); + return prevState; + }); + } + + renderDate = (date) => { + const d = new Date(date); + return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; + } + + onStatusClick = (closeStream, id, statusMenuOpen) => () => { + if (statusMenuOpen) { + this.setState(prev => { + prev.statusMenus[id] = false; + return prev; + }); + this.props.updateAssetState(id, closeStream ? Date.now() : null) + .then(() => { + const {search, sort, filter, page} = this.state; + this.props.fetchAssets(page, limit, search, sort, filter); + }); + } else { + this.setState(prev => { + prev.statusMenus[id] = true; + return prev; + }); + } + } + + renderTitle = (title, {id}) => {title} + + renderStatus = (closedAt, {id}) => { + const closed = closedAt && new Date(closedAt).getTime() < Date.now(); + const statusMenuOpen = this.state.statusMenus[id]; + return
+
+ {!statusMenuOpen && } + {closed ? lang.t('streams.closed') : lang.t('streams.open')} +
+ { + statusMenuOpen && +
+ {!closed ? lang.t('streams.closed') : lang.t('streams.open')} +
+ } +
; + } + + onPageClick = (page) => { + this.setState({page}); + const {search, sort, filter} = this.state; + this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter); + } + + render () { + const {search, sort, filter} = this.state; + const {assets} = this.props; + + const assetsIds = assets.ids.map((id) => assets.byId[id]); + + return ( +
+
+
+ + +
+
{lang.t('streams.filter-streams')}
+
{lang.t('streams.stream-status')}
+ + {lang.t('streams.all')} + {lang.t('streams.open')} + {lang.t('streams.closed')} + +
{lang.t('streams.sort-by')}
+ + {lang.t('streams.newest')} + {lang.t('streams.oldest')} + +
+ { + assetsIds.length + ?
+ + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + + {lang.t('streams.status')} + + + +
+ : {lang.t('streams.empty_result')} + } +
+ ); + } +} + +const mapStateToProps = ({assets}) => { + return { + assets: assets.toJS() + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + fetchAssets: (...args) => { + dispatch(fetchAssets.apply(this, args)); + }, + updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args)) + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Stories); + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index 25c7900bc..73ccc4da4 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -94,7 +94,7 @@ "moderate": "Moderate", "configure": "Configure", "community": "Community", - "streams": "Streams", + "stories": "Stories", "closed-comments-desc": "Write a message to be displayed when when your comment stream is closed and no longer accepting comments.", "closed-comments-label": "Write a message...", "hours": "Hours", @@ -152,9 +152,9 @@ "sort-by": "Sort By", "open": "Open", "closed": "Closed", - "article": "Article", + "article": "Story", "pubdate": "Publication Date", - "status": "Status" + "status": "Stream Status" } }, "es": { @@ -253,7 +253,7 @@ "moderate": "Moderar", "configure": "Configurar", "community": "Comunidad", - "streams": "Streams", + "stories": "Artículos", "closed-comments-desc": "Escribe un mensaje que será mostrado cuando los comentarios estén cerrados y no se acepten más comentarios.", "closed-comments-label": "Escribe un mensaje...", "never": "Nunca", @@ -300,7 +300,7 @@ "sort-by": "", "open": "", "closed": "", - "article": "", + "article": "artículo", "pubdate": "", "status": "" } From e975d6d8095084216518efded9dba58ed1081372 Mon Sep 17 00:00:00 2001 From: riley Date: Wed, 15 Mar 2017 10:15:47 -0600 Subject: [PATCH 44/56] move pre-mod links --- .../containers/Configure/ModerationSettings.js | 18 ++++++++++++++++++ .../src/containers/Configure/StreamSettings.js | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/ModerationSettings.js b/client/coral-admin/src/containers/Configure/ModerationSettings.js index da158b612..5b7286b98 100644 --- a/client/coral-admin/src/containers/Configure/ModerationSettings.js +++ b/client/coral-admin/src/containers/Configure/ModerationSettings.js @@ -16,6 +16,11 @@ const updateEmailConfirmation = (updateSettings, verify) => () => { updateSettings({requireEmailConfirmation: !verify}); }; +const updatePremodLinksEnable = (updateSettings, premodLinks) => () => { + const premodLinksEnable = !premodLinks; + updateSettings({premodLinksEnable}); +}; + const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => { // just putting this here for shorthand below @@ -50,6 +55,19 @@ const ModerationSettings = ({settings, updateSettings, onChangeWordlist}) => {

+ +
+ +
+
+
{lang.t('configure.enable-premod-links')}
+

+ {lang.t('configure.enable-premod-links-text')} +

+
+
() => { updateSettings({infoBoxEnable}); }; -const updatePremodLinksEnable = (updateSettings, premodLinks) => () => { - const premodLinksEnable = !premodLinks; - updateSettings({premodLinksEnable}); -}; - const updateInfoBoxContent = (updateSettings) => (event) => { const infoBoxContent = event.target.value; updateSettings({infoBoxContent}); @@ -99,19 +94,6 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {

- -
- -
-
-
{lang.t('configure.enable-premod-links')}
-

- {lang.t('configure.enable-premod-links-text')} -

-
-
Date: Wed, 15 Mar 2017 11:16:13 -0600 Subject: [PATCH 45/56] correctly handle error when not logged in --- client/coral-admin/src/actions/auth.js | 20 ++++++++++++++------ client/coral-framework/actions/auth.js | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 389e8b4d2..dd2eaa7dc 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -5,9 +5,13 @@ import coralApi from 'coral-framework/helpers/response'; export const handleLogin = (email, password) => dispatch => { dispatch({type: actions.LOGIN_REQUEST}); return coralApi('/auth/local', {method: 'POST', body: {email, password}}) - .then(result => { - const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length; - dispatch(checkLoginSuccess(result.user, isAdmin)); + .then(({user}) => { + if (!user) { + return dispatch(checkLoginFailure('not logged in')); + } + + const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length; + dispatch(checkLoginSuccess(user, isAdmin)); }) .catch(error => { dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key}); @@ -34,9 +38,13 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error}); export const checkLogin = () => dispatch => { dispatch(checkLoginRequest()); return coralApi('/auth') - .then(result => { - const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length; - dispatch(checkLoginSuccess(result.user, isAdmin)); + .then(({user}) => { + if (!user) { + return dispatch(checkLoginFailure('not logged in')); + } + + const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length; + dispatch(checkLoginSuccess(user, isAdmin)); }) .catch(error => { console.error(error); diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index ba1952e29..9dedaa2f2 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -48,7 +48,7 @@ export const fetchSignIn = (formData) => (dispatch) => { dispatch(signInRequest()); return coralApi('/auth/local', {method: 'POST', body: formData}) .then(({user}) => { - const isAdmin = !!user.roles.filter(i => i === 'ADMIN').length; + const isAdmin = !!user && !!user.roles.filter(i => i === 'ADMIN').length; dispatch(signInSuccess(user, isAdmin)); dispatch(hideSignInDialog()); }) From a2833538a74ae08b8616bc9a2bf17ce56ebdf2d2 Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 20 Mar 2017 10:26:12 -0700 Subject: [PATCH 46/56] move CountdownTimer to its own component --- .../src/components/CountdownTimer.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 client/coral-admin/src/components/CountdownTimer.js diff --git a/client/coral-admin/src/components/CountdownTimer.js b/client/coral-admin/src/components/CountdownTimer.js new file mode 100644 index 000000000..3eaa6d305 --- /dev/null +++ b/client/coral-admin/src/components/CountdownTimer.js @@ -0,0 +1,79 @@ +import React, {PropTypes} from 'react'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from 'coral-admin/src/translations'; +import styles from 'coral-admin/src/containers/Dashboard/Dashboard.css'; +import {Icon} from 'coral-ui'; + +const lang = new I18n(translations); +const refreshIntervalSeconds = 60 * 5; + +class CountdownTimer extends React.Component { + + static propTypes = { + handleTimeout: PropTypes.func.isRequired + } + + constructor (props) { + super(props); + try { + if (window.localStorage.getItem('coral:dashboardNote') === null) { + window.localStorage.setItem('coral:dashboardNote', 'show'); + } + } catch (e) { + + // above will fail in Private Mode in some browsers. + } + + this.state = { + secondsUntilRefresh: refreshIntervalSeconds, + dashboardNote: window.localStorage.getItem('coral:dashboardNote') || 'show' + }; + } + + componentWillMount () { + setInterval(() => { // the countdown timer + let nextCount = this.state.secondsUntilRefresh - 1; + if (nextCount < 0) { + nextCount = refreshIntervalSeconds; + this.props.handleTimeout(); + } + this.setState({secondsUntilRefresh: nextCount}); + }, 1000); + } + + formatTime = () => { + const minutes = Math.floor(this.state.secondsUntilRefresh / 60); + let seconds = (this.state.secondsUntilRefresh % 60).toString(); + if (seconds.length < 2) { + seconds = `0${seconds}`; + } + + return `${minutes}:${seconds}`; + } + + dismissNote = () => { + try { + window.localStorage.setItem('coral:dashboardNote', 'hide'); + } catch (e) { + + // when setItem fails in Safari Private mode + this.setState({dashboardNote: 'hide'}); + } + } + + render () { + const hideReloadNote = window.localStorage.getItem('coral:dashboardNote') === 'hide' || + this.state.dashboardNote === 'hide'; // for Safari Incognito + return ( +

+ × + {lang.t('dashboard.next-update', this.formatTime())} {lang.t('dashboard.auto-update')} +

+ ); + } +} + +export default CountdownTimer; From d2e2eef046e3b2354a43a966ff9dd8e5436fe7f6 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Mar 2017 09:28:17 -0600 Subject: [PATCH 47/56] do a null check. --- client/coral-admin/src/containers/Dashboard/FlagWidget.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/containers/Dashboard/FlagWidget.js index 468875d3d..17be2acbd 100644 --- a/client/coral-admin/src/containers/Dashboard/FlagWidget.js +++ b/client/coral-admin/src/containers/Dashboard/FlagWidget.js @@ -19,7 +19,11 @@ const FlagWidget = ({assets}) => { { assets.length ? assets.map(asset => { - const flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary'); + let flagSummary = null; + if (asset.action_summaries) { + flagSummary = asset.action_summaries.find(s => s.type === 'FlagAssetActionSummary'); + } + return (
Moderate From 517ede1977e84c0eb90465bdc53928cea0c552c6 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Mar 2017 11:45:10 -0600 Subject: [PATCH 48/56] dashboard reload fix --- client/coral-admin/src/components/CountdownTimer.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/src/components/CountdownTimer.js b/client/coral-admin/src/components/CountdownTimer.js index 3eaa6d305..aba4d0b5a 100644 --- a/client/coral-admin/src/components/CountdownTimer.js +++ b/client/coral-admin/src/components/CountdownTimer.js @@ -31,16 +31,20 @@ class CountdownTimer extends React.Component { } componentWillMount () { - setInterval(() => { // the countdown timer + this.interval = setInterval(() => { // the countdown timer let nextCount = this.state.secondsUntilRefresh - 1; if (nextCount < 0) { nextCount = refreshIntervalSeconds; - this.props.handleTimeout(); + return this.props.handleTimeout(); } this.setState({secondsUntilRefresh: nextCount}); }, 1000); } + componentWillUnmount () { + window.clearInterval(this.interval); + } + formatTime = () => { const minutes = Math.floor(this.state.secondsUntilRefresh / 60); let seconds = (this.state.secondsUntilRefresh % 60).toString(); From 73c59cb137f6c6f4541f7739de5d62989ff84a75 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Mar 2017 14:37:29 -0600 Subject: [PATCH 49/56] show different copy per # of replies --- client/coral-embed-stream/src/Comment.js | 1 + client/coral-embed-stream/src/LoadMore.js | 44 +++++++++++++++------ client/coral-embed-stream/style/default.css | 2 + client/coral-framework/translations.json | 8 +++- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 20060f474..afbcd0030 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -243,6 +243,7 @@ class Comment extends React.Component { assetId={asset.id} comments={comment.replies} parentId={comment.id} + replyCount={comment.replyCount} moreComments={comment.replyCount > comment.replies.length} loadMore={loadMore}/>
diff --git a/client/coral-embed-stream/src/LoadMore.js b/client/coral-embed-stream/src/LoadMore.js index e33828bc1..66b06495a 100644 --- a/client/coral-embed-stream/src/LoadMore.js +++ b/client/coral-embed-stream/src/LoadMore.js @@ -24,22 +24,44 @@ const loadMoreComments = (assetId, comments, loadMore, parentId) => { }); }; -const LoadMore = ({assetId, comments, loadMore, moreComments, parentId}) => ( - moreComments - ? - : null -); +class LoadMore extends React.Component { + + componentDidMount () { + this.initialState = true; + } + + replyCountFormat = (count) => { + if (count === 1) { + return lang.t('viewReply'); + } + + if (this.initialState) { + return lang.t('viewAllRepliesInitial', count); + } else { + return lang.t('viewAllReplies', count); + } + } + + render () { + const {assetId, comments, loadMore, moreComments, parentId, replyCount} = this.props; + return moreComments + ? + : null; + } +} LoadMore.propTypes = { assetId: PropTypes.string.isRequired, comments: PropTypes.array.isRequired, moreComments: PropTypes.bool.isRequired, + replyCount: PropTypes.number.isRequired, loadMore: PropTypes.func.isRequired }; diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 68bac3110..629efd29f 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -425,6 +425,8 @@ button.coral-load-more { cursor: pointer; padding: 10px; border-radius: 2px; + line-height: 1em; + text-transform: capitalize; } button.coral-load-more:hover { diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index d95f5c9ec..fa6899eb8 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -4,7 +4,6 @@ "successUpdateSettings": "The changes you have made have been applied to the comment stream on this article", "successNameUpdate": "Your username has been updated", "contentNotAvailable": "This content is not available", - "loadMore": "View more", "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Report, or write comments. Please contact us if you have any questions.", "editName": { "msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. Please contact us if you have any questions.", @@ -12,6 +11,9 @@ "button": "Submit", "error": "Usernames can contain letters, numbers and _ only" }, + "viewReply": "view reply", + "viewAllRepliesInitial": "view all {0} replies", + "viewAllReplies": "view {0} replies", "newCount": "View {0} more {1}", "comment": "comment", "comments": "comments", @@ -43,7 +45,9 @@ "contentNotAvailable": "El contenido no se encuentra disponible", "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios.", "editNameMsg": "", - "loadMore": "Ver más", + "viewReply": "", + "viewAllRepliesInitial": "", + "viewAllReplies": "", "newCount": "Ver {0} {1} más", "comment": "commentario", "comments": "commentarios", From 316aa12481c15ff4a82e83d86763c51987f757e1 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Mar 2017 14:54:09 -0600 Subject: [PATCH 50/56] add spanish translations --- client/coral-framework/translations.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index fa6899eb8..ee04dcadb 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -45,9 +45,9 @@ "contentNotAvailable": "El contenido no se encuentra disponible", "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios.", "editNameMsg": "", - "viewReply": "", - "viewAllRepliesInitial": "", - "viewAllReplies": "", + "viewReply": "ver respuesta", + "viewAllRepliesInitial": "ver todas las {0} respuestas", + "viewAllReplies": "ver {0} respuestas", "newCount": "Ver {0} {1} más", "comment": "commentario", "comments": "commentarios", From 9d64639f8ede9b73f68cf74be099e705365341ec Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 16 Mar 2017 15:35:38 -0600 Subject: [PATCH 51/56] differentiate between top- and comment-level LoadMore --- client/coral-embed-stream/src/Comment.js | 3 ++- client/coral-embed-stream/src/Embed.js | 1 + client/coral-embed-stream/src/LoadMore.js | 7 ++++--- client/coral-framework/translations.json | 2 ++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index afbcd0030..06352ec2f 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -232,7 +232,7 @@ class Comment extends React.Component { removeCommentTag={removeCommentTag} showSignInDialog={showSignInDialog} reactKey={reply.id} - key={reply.id} + key={`${reply.id}:${depth}`} comment={reply} />; }) } @@ -243,6 +243,7 @@ class Comment extends React.Component { assetId={asset.id} comments={comment.replies} parentId={comment.id} + topLevel={false} replyCount={comment.replyCount} moreComments={comment.replyCount > comment.replies.length} loadMore={loadMore}/> diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 0b9bc4fc5..ce6dc638a 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -221,6 +221,7 @@ class Embed extends Component { comments={asset.comments} />
asset.comments.length} diff --git a/client/coral-embed-stream/src/LoadMore.js b/client/coral-embed-stream/src/LoadMore.js index 66b06495a..bf89793d0 100644 --- a/client/coral-embed-stream/src/LoadMore.js +++ b/client/coral-embed-stream/src/LoadMore.js @@ -43,7 +43,7 @@ class LoadMore extends React.Component { } render () { - const {assetId, comments, loadMore, moreComments, parentId, replyCount} = this.props; + const {assetId, comments, loadMore, moreComments, parentId, replyCount, topLevel} = this.props; return moreComments ? : null; } @@ -61,7 +61,8 @@ LoadMore.propTypes = { assetId: PropTypes.string.isRequired, comments: PropTypes.array.isRequired, moreComments: PropTypes.bool.isRequired, - replyCount: PropTypes.number.isRequired, + topLevel: PropTypes.bool.isRequired, + replyCount: PropTypes.number, loadMore: PropTypes.func.isRequired }; diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index ee04dcadb..b327a7097 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -11,6 +11,7 @@ "button": "Submit", "error": "Usernames can contain letters, numbers and _ only" }, + "viewMoreComments": "view more comments", "viewReply": "view reply", "viewAllRepliesInitial": "view all {0} replies", "viewAllReplies": "view {0} replies", @@ -45,6 +46,7 @@ "contentNotAvailable": "El contenido no se encuentra disponible", "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios.", "editNameMsg": "", + "viewMoreComments": "Var commentarios más", "viewReply": "ver respuesta", "viewAllRepliesInitial": "ver todas las {0} respuestas", "viewAllReplies": "ver {0} respuestas", From 5768d30a7a84b1c92bf75aa987c8715887e0df7a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 16 Mar 2017 11:07:38 -0600 Subject: [PATCH 52/56] Added new pageData action/reducer --- client/coral-admin/src/actions/pageData.js | 7 +++++++ client/coral-admin/src/components/AdminLogin.js | 7 ++++--- .../src/containers/LayoutContainer.js | 15 +++++++++++++-- client/coral-admin/src/reducers/index.js | 4 +++- client/coral-admin/src/reducers/pageData.js | 16 ++++++++++++++++ routes/admin/index.js | 6 +++++- routes/embed/index.js | 6 +++++- views/admin.ejs | 3 +++ views/embed/stream.ejs | 10 +++++++++- webpack.config.js | 3 --- 10 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 client/coral-admin/src/actions/pageData.js create mode 100644 client/coral-admin/src/reducers/pageData.js diff --git a/client/coral-admin/src/actions/pageData.js b/client/coral-admin/src/actions/pageData.js new file mode 100644 index 000000000..beaef6e60 --- /dev/null +++ b/client/coral-admin/src/actions/pageData.js @@ -0,0 +1,7 @@ +export const PAGE_DATA_UPDATED = 'PAGE_DATA_UPDATED'; + +export const fetchPageData = () => dispatch => { + let json = document.getElementById('data'); + let data = JSON.parse(json.textContent); + dispatch({type: PAGE_DATA_UPDATED, data}); +}; diff --git a/client/coral-admin/src/components/AdminLogin.js b/client/coral-admin/src/components/AdminLogin.js index 11e840eb7..e7e41eb48 100644 --- a/client/coral-admin/src/components/AdminLogin.js +++ b/client/coral-admin/src/components/AdminLogin.js @@ -34,7 +34,7 @@ class AdminLogin extends React.Component { } render () { - const {errorMessage, loginMaxExceeded} = this.props; + const {errorMessage, loginMaxExceeded, recaptchaPublic} = this.props; const signInForm = (
{errorMessage && {lang.t(`errors.${errorMessage}`)}} @@ -62,7 +62,7 @@ class AdminLogin extends React.Component { { loginMaxExceeded && ; } if (!isAdmin) { @@ -27,6 +35,7 @@ class LayoutContainer extends Component { handleLogin={this.props.handleLogin} requestPasswordReset={this.props.requestPasswordReset} passwordRequestSuccess={passwordRequestSuccess} + recaptchaPublic={TALK_RECAPTCHA_PUBLIC} errorMessage={loginError} />; } if (isAdmin && loggedIn) { return ; } @@ -35,11 +44,13 @@ class LayoutContainer extends Component { } const mapStateToProps = state => ({ - auth: state.auth.toJS() + auth: state.auth.toJS(), + TALK_RECAPTCHA_PUBLIC: state.pageData.get('data').get('TALK_RECAPTCHA_PUBLIC', null) }); const mapDispatchToProps = dispatch => ({ checkLogin: () => dispatch(checkLogin()), + fetchPageData: () => dispatch(fetchPageData()), handleLogin: (username, password, recaptchaResponse) => dispatch(handleLogin(username, password, recaptchaResponse)), requestPasswordReset: email => dispatch(requestPasswordReset(email)), handleLogout: () => dispatch(logout()) diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 837ad507d..33be4f384 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -4,6 +4,7 @@ import settings from './settings'; import community from './community'; import moderation from './moderation'; import install from './install'; +import pageData from './pageData'; export default { auth, @@ -11,5 +12,6 @@ export default { settings, community, moderation, - install + install, + pageData }; diff --git a/client/coral-admin/src/reducers/pageData.js b/client/coral-admin/src/reducers/pageData.js new file mode 100644 index 000000000..322d092bd --- /dev/null +++ b/client/coral-admin/src/reducers/pageData.js @@ -0,0 +1,16 @@ +import {Map} from 'immutable'; + +import * as actions from '../actions/pageData'; + +const initialState = Map({ + data: Map({}) +}); + +export default function pageData (state = initialState, action) { + switch (action.type) { + case actions.PAGE_DATA_UPDATED: + return state.set('data', Map(action.data)); + default: + return state; + } +} diff --git a/routes/admin/index.js b/routes/admin/index.js index cb256327a..d250ea32a 100644 --- a/routes/admin/index.js +++ b/routes/admin/index.js @@ -16,7 +16,11 @@ router.get('/password-reset', (req, res) => { }); router.get('*', (req, res) => { - res.render('admin', {basePath: '/client/coral-admin'}); + const data = { + TALK_RECAPTCHA_PUBLIC: process.env.TALK_RECAPTCHA_PUBLIC + }; + + res.render('admin', {basePath: '/client/coral-admin', data}); }); module.exports = router; diff --git a/routes/embed/index.js b/routes/embed/index.js index ccc872c1f..efa563422 100644 --- a/routes/embed/index.js +++ b/routes/embed/index.js @@ -7,7 +7,11 @@ router.use('/:embed', (req, res, next) => { case 'stream': return SettingsService.retrieve() .then(({customCssUrl}) => { - return res.render('embed/stream', {customCssUrl}); + const data = { + TALK_RECAPTCHA_PUBLIC: process.env.TALK_RECAPTCHA_PUBLIC + }; + + return res.render('embed/stream', {customCssUrl, data}); }); default: diff --git a/views/admin.ejs b/views/admin.ejs index d4d635dcc..36e3d15c8 100644 --- a/views/admin.ejs +++ b/views/admin.ejs @@ -82,6 +82,9 @@ } + <% if (data != null) { %> + + <% } %>
diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs index fe7baf919..4c300f6d6 100644 --- a/views/embed/stream.ejs +++ b/views/embed/stream.ejs @@ -8,9 +8,17 @@ <% if (locals.customCssUrl) { %> <% } %> + <% if (data != null) { %> + + <% } %> +
- + <%# %> + diff --git a/webpack.config.js b/webpack.config.js index e7652a67d..432bb6155 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -118,9 +118,6 @@ module.exports = { 'process.env': { 'VERSION': `"${require('./package.json').version}"` } - }), - new webpack.EnvironmentPlugin({ - TALK_RECAPTCHA_PUBLIC: JSON.stringify(process.env.TALK_RECAPTCHA_PUBLIC) }) ], resolve: { From dfffbbe61984a0a699bd52947c09326318d6f9f4 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 16 Mar 2017 11:11:24 -0600 Subject: [PATCH 53/56] bug fix --- views/embed/stream.ejs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs index 4c300f6d6..129895fc4 100644 --- a/views/embed/stream.ejs +++ b/views/embed/stream.ejs @@ -15,10 +15,6 @@
- <%# %> - + From 46ecf0c06ff76a8793ce84dec1fd134710a83a05 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 17 Mar 2017 08:37:58 -0600 Subject: [PATCH 54/56] Renamed pageData -> config --- client/coral-admin/src/actions/config.js | 7 +++++++ client/coral-admin/src/actions/pageData.js | 7 ------- client/coral-admin/src/containers/LayoutContainer.js | 10 +++++----- .../src/reducers/{pageData.js => config.js} | 6 +++--- client/coral-admin/src/reducers/index.js | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 client/coral-admin/src/actions/config.js delete mode 100644 client/coral-admin/src/actions/pageData.js rename client/coral-admin/src/reducers/{pageData.js => config.js} (56%) diff --git a/client/coral-admin/src/actions/config.js b/client/coral-admin/src/actions/config.js new file mode 100644 index 000000000..dca0f7117 --- /dev/null +++ b/client/coral-admin/src/actions/config.js @@ -0,0 +1,7 @@ +export const CONFIG_UPDATED = 'CONFIG_UPDATED'; + +export const fetchConfig = () => dispatch => { + let json = document.getElementById('data'); + let data = JSON.parse(json.textContent); + dispatch({type: CONFIG_UPDATED, data}); +}; diff --git a/client/coral-admin/src/actions/pageData.js b/client/coral-admin/src/actions/pageData.js deleted file mode 100644 index beaef6e60..000000000 --- a/client/coral-admin/src/actions/pageData.js +++ /dev/null @@ -1,7 +0,0 @@ -export const PAGE_DATA_UPDATED = 'PAGE_DATA_UPDATED'; - -export const fetchPageData = () => dispatch => { - let json = document.getElementById('data'); - let data = JSON.parse(json.textContent); - dispatch({type: PAGE_DATA_UPDATED, data}); -}; diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/LayoutContainer.js index fefcaaf28..81f976f06 100644 --- a/client/coral-admin/src/containers/LayoutContainer.js +++ b/client/coral-admin/src/containers/LayoutContainer.js @@ -2,16 +2,16 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import Layout from '../components/ui/Layout'; import {checkLogin, handleLogin, logout, requestPasswordReset} from '../actions/auth'; -import {fetchPageData} from '../actions/pageData'; +import {fetchConfig} from '../actions/config'; import {FullLoading} from '../components/FullLoading'; import AdminLogin from '../components/AdminLogin'; class LayoutContainer extends Component { componentWillMount () { - const {checkLogin, fetchPageData} = this.props; + const {checkLogin, fetchConfig} = this.props; checkLogin(); - fetchPageData(); + fetchConfig(); } render () { const { @@ -45,12 +45,12 @@ class LayoutContainer extends Component { const mapStateToProps = state => ({ auth: state.auth.toJS(), - TALK_RECAPTCHA_PUBLIC: state.pageData.get('data').get('TALK_RECAPTCHA_PUBLIC', null) + TALK_RECAPTCHA_PUBLIC: state.config.get('data').get('TALK_RECAPTCHA_PUBLIC', null) }); const mapDispatchToProps = dispatch => ({ checkLogin: () => dispatch(checkLogin()), - fetchPageData: () => dispatch(fetchPageData()), + fetchConfig: () => dispatch(fetchConfig()), handleLogin: (username, password, recaptchaResponse) => dispatch(handleLogin(username, password, recaptchaResponse)), requestPasswordReset: email => dispatch(requestPasswordReset(email)), handleLogout: () => dispatch(logout()) diff --git a/client/coral-admin/src/reducers/pageData.js b/client/coral-admin/src/reducers/config.js similarity index 56% rename from client/coral-admin/src/reducers/pageData.js rename to client/coral-admin/src/reducers/config.js index 322d092bd..a5d11d6eb 100644 --- a/client/coral-admin/src/reducers/pageData.js +++ b/client/coral-admin/src/reducers/config.js @@ -1,14 +1,14 @@ import {Map} from 'immutable'; -import * as actions from '../actions/pageData'; +import * as actions from '../actions/config'; const initialState = Map({ data: Map({}) }); -export default function pageData (state = initialState, action) { +export default function config (state = initialState, action) { switch (action.type) { - case actions.PAGE_DATA_UPDATED: + case actions.CONFIG_UPDATED: return state.set('data', Map(action.data)); default: return state; diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 33be4f384..3036d6271 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -4,7 +4,7 @@ import settings from './settings'; import community from './community'; import moderation from './moderation'; import install from './install'; -import pageData from './pageData'; +import config from './config'; export default { auth, @@ -13,5 +13,5 @@ export default { community, moderation, install, - pageData + config }; From 09ec2dba94c214c266ba628376f8ef07d18bd7a8 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 17 Mar 2017 10:18:07 -0600 Subject: [PATCH 55/56] combine destructuring --- client/coral-admin/src/containers/LayoutContainer.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/LayoutContainer.js index 81f976f06..30e32a828 100644 --- a/client/coral-admin/src/containers/LayoutContainer.js +++ b/client/coral-admin/src/containers/LayoutContainer.js @@ -23,11 +23,7 @@ class LayoutContainer extends Component { passwordRequestSuccess } = this.props.auth; - const { - TALK_RECAPTCHA_PUBLIC - } = this.props; - - const {handleLogout} = this.props; + const {handleLogout, TALK_RECAPTCHA_PUBLIC} = this.props; if (loadingUser) { return ; } if (!isAdmin) { return Date: Fri, 17 Mar 2017 10:46:45 -0600 Subject: [PATCH 56/56] Update Dockerfile --- Dockerfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index eb3cc9c60..f8cb8a45d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,12 +12,9 @@ EXPOSE 5000 # Install app dependencies COPY package.json yarn.lock /usr/src/app/ -RUN yarn install +RUN yarn install --production # Bundle app source COPY . /usr/src/app -# Build static assets -RUN yarn build - CMD ["yarn", "start"]