diff --git a/migrations/20180125_clean_new_notifications.js b/migrations/20180125_clean_new_notifications.js
new file mode 100644
index 0000000000..4850c0c726
--- /dev/null
+++ b/migrations/20180125_clean_new_notifications.js
@@ -0,0 +1,93 @@
+const UserNotification = require('../website/server/models/userNotification').model;
+const content = require('../website/common/script/content/index');
+
+const migrationName = '20180125_clean_new_migrations';
+const authorName = 'paglias'; // in case script author needs to know when their ...
+const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
+
+/*
+ * Clean new migration types for processed users
+ */
+
+const monk = require('monk');
+const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
+const dbUsers = monk(connectionString).get('users', { castIds: false });
+
+const progressCount = 1000;
+let count = 0;
+
+function updateUser (user) {
+ count++;
+
+ const types = ['NEW_MYSTERY_ITEMS', 'CARD_RECEIVED', 'NEW_CHAT_MESSAGE'];
+
+ dbUsers.update({_id: user._id}, {
+ $pull: {notifications: { type: {$in: types } } },
+ $set: {migration: migrationName},
+ });
+
+ if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
+ if (user._id === authorUuid) console.warn(`${authorName } processed`);
+}
+
+function exiting (code, msg) {
+ code = code || 0; // 0 = success
+ if (code && !msg) {
+ msg = 'ERROR!';
+ }
+ if (msg) {
+ if (code) {
+ console.error(msg);
+ } else {
+ console.log(msg);
+ }
+ }
+ process.exit(code);
+}
+
+function displayData () {
+ console.warn(`\n${ count } users processed\n`);
+ return exiting(0);
+}
+
+function updateUsers (users) {
+ if (!users || users.length === 0) {
+ console.warn('All appropriate users found and modified.');
+ displayData();
+ return;
+ }
+
+ const userPromises = users.map(updateUser);
+ const lastUser = users[users.length - 1];
+
+ return Promise.all(userPromises)
+ .then(() => {
+ processUsers(lastUser._id);
+ });
+}
+
+function processUsers (lastId) {
+ // specify a query to limit the affected users (empty for all users):
+ const query = {
+ migration: {$ne: migrationName},
+ 'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')},
+ };
+
+ if (lastId) {
+ query._id = {
+ $gt: lastId,
+ };
+ }
+
+ dbUsers.find(query, {
+ sort: {_id: 1},
+ limit: 250,
+ })
+ .then(updateUsers)
+ .catch((err) => {
+ console.log(err);
+ return exiting(1, `ERROR! ${ err}`);
+ });
+}
+
+module.exports = processUsers;
diff --git a/migrations/20180125_notifications.js b/migrations/20180125_notifications.js
new file mode 100644
index 0000000000..7e7f697dab
--- /dev/null
+++ b/migrations/20180125_notifications.js
@@ -0,0 +1,149 @@
+const UserNotification = require('../website/server/models/userNotification').model;
+const content = require('../website/common/script/content/index');
+
+const migrationName = '20180125_migrations-v2';
+const authorName = 'paglias'; // in case script author needs to know when their ...
+const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
+
+/*
+ * Migrate to new notifications system
+ */
+
+const monk = require('monk');
+const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
+const dbUsers = monk(connectionString).get('users', { castIds: false });
+
+const progressCount = 1000;
+let count = 0;
+
+function updateUser (user) {
+ count++;
+
+ const notifications = [];
+
+ // UNALLOCATED_STATS_POINTS skipped because added on each save
+ // NEW_STUFF skipped because it's a new type
+ // GROUP_TASK_NEEDS_WORK because it's a new type
+ // NEW_INBOX_MESSAGE not implemented yet
+
+
+ // NEW_MYSTERY_ITEMS
+ const mysteryItems = user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems;
+ if (Array.isArray(mysteryItems) && mysteryItems.length > 0) {
+ const newMysteryNotif = new UserNotification({
+ type: 'NEW_MYSTERY_ITEMS',
+ data: {
+ items: mysteryItems,
+ },
+ }).toJSON();
+ notifications.push(newMysteryNotif);
+ }
+
+ // CARD_RECEIVED
+ Object.keys(content.cardTypes).forEach(cardType => {
+ const existingCards = user.items.special[`${cardType}Received`] || [];
+ existingCards.forEach(sender => {
+ const newNotif = new UserNotification({
+ type: 'CARD_RECEIVED',
+ data: {
+ card: cardType,
+ from: {
+ // id is missing in old notifications
+ name: sender,
+ },
+ },
+ }).toJSON();
+
+ notifications.push(newNotif);
+ });
+ });
+
+ // NEW_CHAT_MESSAGE
+ Object.keys(user.newMessages).forEach(groupId => {
+ const existingNotif = user.newMessages[groupId];
+
+ if (existingNotif) {
+ const newNotif = new UserNotification({
+ type: 'NEW_CHAT_MESSAGE',
+ data: {
+ group: {
+ id: groupId,
+ name: existingNotif.name,
+ },
+ },
+ }).toJSON();
+
+ notifications.push(newNotif);
+ }
+ });
+
+ dbUsers.update({_id: user._id}, {
+ $push: {notifications: { $each: notifications } },
+ $set: {migration: migrationName},
+ });
+
+ if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
+ if (user._id === authorUuid) console.warn(`${authorName } processed`);
+}
+
+function exiting (code, msg) {
+ code = code || 0; // 0 = success
+ if (code && !msg) {
+ msg = 'ERROR!';
+ }
+ if (msg) {
+ if (code) {
+ console.error(msg);
+ } else {
+ console.log(msg);
+ }
+ }
+ process.exit(code);
+}
+
+function displayData () {
+ console.warn(`\n${ count } users processed\n`);
+ return exiting(0);
+}
+
+function updateUsers (users) {
+ if (!users || users.length === 0) {
+ console.warn('All appropriate users found and modified.');
+ displayData();
+ return;
+ }
+
+ const userPromises = users.map(updateUser);
+ const lastUser = users[users.length - 1];
+
+ return Promise.all(userPromises)
+ .then(() => {
+ processUsers(lastUser._id);
+ });
+}
+
+function processUsers (lastId) {
+ // specify a query to limit the affected users (empty for all users):
+ const query = {
+ migration: {$ne: migrationName},
+ 'auth.timestamps.loggedin': {$gt: new Date('2010-01-24')},
+ };
+
+ if (lastId) {
+ query._id = {
+ $gt: lastId,
+ };
+ }
+
+ dbUsers.find(query, {
+ sort: {_id: 1},
+ limit: 250,
+ })
+ .then(updateUsers)
+ .catch((err) => {
+ console.log(err);
+ return exiting(1, `ERROR! ${ err}`);
+ });
+}
+
+module.exports = processUsers;
diff --git a/migrations/migration-runner.js b/migrations/migration-runner.js
index b594e403d2..2b030b88e6 100644
--- a/migrations/migration-runner.js
+++ b/migrations/migration-runner.js
@@ -17,5 +17,5 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
-const processUsers = require('./tasks/tasks-set-everyX');
+const processUsers = require('./20180125_clean_new_notifications.js');
processUsers();
diff --git a/migrations/mystery_items.js b/migrations/mystery_items.js
index df34e173ef..28da1c7ed6 100644
--- a/migrations/mystery_items.js
+++ b/migrations/mystery_items.js
@@ -1,10 +1,23 @@
+var UserNotification = require('../website/server/models/userNotification').model
+
var _id = '';
+
+var items = ['back_mystery_201801','headAccessory_mystery_201801']
+
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
- $each:['back_mystery_201801','headAccessory_mystery_201801']
+ $each: items,
}
- }
+ },
+ $push: {
+ notifications: (new UserNotification({
+ type: 'NEW_MYSTERY_ITEMS',
+ data: {
+ items: items,
+ },
+ })).toJSON(),
+ },
};
/*var update = {
diff --git a/package-lock.json b/package-lock.json
index 0cea35de7d..3dce45a455 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -408,6 +408,14 @@
}
}
},
+ "ansi-colors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.0.1.tgz",
+ "integrity": "sha512-yopkAU0ZD/WQ56Tms3xLn6jRuX3SyUMAVi0FdmDIbmmnHW3jHiI1sQFdUl3gfVddjnrsP3Y6ywFKvCRopvoVIA==",
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"ansi-escapes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
@@ -739,7 +747,7 @@
"integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=",
"requires": {
"browserslist": "1.7.7",
- "caniuse-db": "1.0.30000793",
+ "caniuse-db": "1.0.30000799",
"normalize-range": "0.1.2",
"num2fraction": "1.2.2",
"postcss": "5.2.18",
@@ -747,9 +755,9 @@
}
},
"aws-sdk": {
- "version": "2.185.0",
- "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.185.0.tgz",
- "integrity": "sha1-pXC4yxoIPYjtkPX2KRRLHc9uFDQ=",
+ "version": "2.188.0",
+ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.188.0.tgz",
+ "integrity": "sha1-kGKrx9umOTRZ+i80I89dKU8ARhE=",
"requires": {
"buffer": "4.9.1",
"events": "1.1.1",
@@ -806,7 +814,7 @@
"resolved": "https://registry.npmjs.org/axios/-/axios-0.16.2.tgz",
"integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=",
"requires": {
- "follow-redirects": "1.3.0",
+ "follow-redirects": "1.4.1",
"is-buffer": "1.1.6"
}
},
@@ -1663,9 +1671,9 @@
"integrity": "sha512-gulJE5dGFo6Q61V/whS6VM4WIyrlydXfCgkE+Gxe5hjrJ8rXLLZlALq7zq2RPhOc45PSwQpJkrTnc2KgD6cvmA=="
},
"bootstrap-vue": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-1.5.0.tgz",
- "integrity": "sha512-tQ22yKZbvKd2HWbFey1WVXbjJY/3zvEep+ODt/EPC4BFSCcPzq7s6Em/boQ6tIE4xdckySDSgGAtxPcwo4Sz9g==",
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-1.5.1.tgz",
+ "integrity": "sha512-bkob7vTHA5VZN6U0Wj34Yj+6jNtqAtc6MwcsLdBz78fcy8Ju5tlYUYMDUefQ0rQH7hhdtFDn9GuDiqhUPmE1sA==",
"requires": {
"lodash.startcase": "4.4.0",
"opencollective": "1.0.3",
@@ -1858,7 +1866,7 @@
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz",
"integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=",
"requires": {
- "caniuse-db": "1.0.30000793",
+ "caniuse-db": "1.0.30000799",
"electron-to-chromium": "1.3.31"
}
},
@@ -2020,15 +2028,15 @@
"integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=",
"requires": {
"browserslist": "1.7.7",
- "caniuse-db": "1.0.30000793",
+ "caniuse-db": "1.0.30000799",
"lodash.memoize": "4.1.2",
"lodash.uniq": "4.5.0"
}
},
"caniuse-db": {
- "version": "1.0.30000793",
- "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000793.tgz",
- "integrity": "sha1-PADGbkI6ehkHx92Wdpp4sq+opy4="
+ "version": "1.0.30000799",
+ "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000799.tgz",
+ "integrity": "sha1-CQSVPek/P0kmR+WMGhvaenOgyws="
},
"capture-stack-trace": {
"version": "1.0.0",
@@ -2273,9 +2281,9 @@
}
},
"chromedriver": {
- "version": "2.34.1",
- "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.34.1.tgz",
- "integrity": "sha512-ivXrPKKtnX442J8Lkbhb8hJ5+lelzAqrAI9VjVs3/iujm396JnJYXGGGjniPXvQeLVE3HDIWwsHu8goIUq3rMQ==",
+ "version": "2.35.0",
+ "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.35.0.tgz",
+ "integrity": "sha512-zqvC/HKybRxiM68GzByvUaXxTmNCmpETvLQIM92IEdrQxPnONKt3ZdTsiwxmGrL2ZIDbr9OEHJljmhZZMEsFPw==",
"dev": true,
"requires": {
"del": "3.0.0",
@@ -2820,7 +2828,7 @@
"dev": true,
"requires": {
"debug": "2.6.9",
- "encodeurl": "1.0.1",
+ "encodeurl": "1.0.2",
"escape-html": "1.0.3",
"on-finished": "2.3.0",
"parseurl": "1.3.2",
@@ -3115,7 +3123,7 @@
"cipher-base": "1.0.4",
"inherits": "2.0.3",
"ripemd160": "2.0.1",
- "sha.js": "2.4.9"
+ "sha.js": "2.4.10"
}
},
"create-hmac": {
@@ -3128,7 +3136,7 @@
"inherits": "2.0.3",
"ripemd160": "2.0.1",
"safe-buffer": "5.1.1",
- "sha.js": "2.4.9"
+ "sha.js": "2.4.10"
}
},
"cross-env": {
@@ -4192,9 +4200,9 @@
}
},
"domain-browser": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz",
- "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA=="
},
"domain-middleware": {
"version": "0.1.0",
@@ -4627,9 +4635,9 @@
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
},
"encodeurl": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
- "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"encoding": {
"version": "0.1.12",
@@ -4967,7 +4975,7 @@
"imurmurhash": "0.1.4",
"inquirer": "0.12.0",
"is-my-json-valid": "2.17.1",
- "is-resolvable": "1.0.1",
+ "is-resolvable": "1.1.0",
"js-yaml": "3.7.0",
"json-stable-stringify": "1.0.1",
"levn": "0.3.0",
@@ -5095,7 +5103,7 @@
"integrity": "sha1-fPq7yR9b084PAMCpo1Ccorl3O+0=",
"dev": true,
"requires": {
- "eslint-plugin-lodash": "2.5.0",
+ "eslint-plugin-lodash": "2.5.1",
"eslint-plugin-mocha": "4.11.0"
}
},
@@ -5180,9 +5188,9 @@
}
},
"eslint-plugin-lodash": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-2.5.0.tgz",
- "integrity": "sha512-CmNYc6sriYcPwwyv+wUtj6KowIhg9HygMi8ow1Q8qfDM1Y7WaHgZj/kPpT9tpjTJkTO2+goqXXzJRj43m5Eang==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-2.5.1.tgz",
+ "integrity": "sha512-Z1L5ZNE49+2FaEMCoAzik7RQGfuoNvMt/4JdU2XZWyz7juW1qCbxANwfBlfZPe2p1X36JKZL6enJzXZGCBADeQ==",
"dev": true,
"optional": true,
"requires": {
@@ -5573,7 +5581,7 @@
"cookie-signature": "1.0.6",
"debug": "2.2.0",
"depd": "1.1.2",
- "encodeurl": "1.0.1",
+ "encodeurl": "1.0.2",
"escape-html": "1.0.3",
"etag": "1.7.0",
"finalhandler": "0.5.1",
@@ -6109,9 +6117,9 @@
"integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I="
},
"follow-redirects": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.3.0.tgz",
- "integrity": "sha1-9oSHH8EW0uMp/aVe9naH9Pq8kFw=",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz",
+ "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==",
"requires": {
"debug": "3.1.0"
},
@@ -6244,14 +6252,12 @@
"dependencies": {
"abbrev": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
- "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=",
+ "bundled": true,
"optional": true
},
"ajv": {
"version": "4.11.8",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
- "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+ "bundled": true,
"optional": true,
"requires": {
"co": "4.6.0",
@@ -6260,19 +6266,16 @@
},
"ansi-regex": {
"version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ "bundled": true
},
"aproba": {
"version": "1.1.1",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz",
- "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=",
+ "bundled": true,
"optional": true
},
"are-we-there-yet": {
"version": "1.1.4",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
- "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
+ "bundled": true,
"optional": true,
"requires": {
"delegates": "1.0.0",
@@ -6281,43 +6284,36 @@
},
"asn1": {
"version": "0.2.3",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
- "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
+ "bundled": true,
"optional": true
},
"assert-plus": {
"version": "0.2.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
- "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+ "bundled": true,
"optional": true
},
"asynckit": {
"version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+ "bundled": true,
"optional": true
},
"aws-sign2": {
"version": "0.6.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
- "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+ "bundled": true,
"optional": true
},
"aws4": {
"version": "1.6.0",
- "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
- "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
+ "bundled": true,
"optional": true
},
"balanced-match": {
"version": "0.4.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
- "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
+ "bundled": true
},
"bcrypt-pbkdf": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
- "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
+ "bundled": true,
"optional": true,
"requires": {
"tweetnacl": "0.14.5"
@@ -6325,24 +6321,21 @@
},
"block-stream": {
"version": "0.0.9",
- "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
- "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+ "bundled": true,
"requires": {
"inherits": "2.0.3"
}
},
"boom": {
"version": "2.10.1",
- "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
- "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+ "bundled": true,
"requires": {
"hoek": "2.16.3"
}
},
"brace-expansion": {
"version": "1.1.7",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
- "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=",
+ "bundled": true,
"requires": {
"balanced-match": "0.4.2",
"concat-map": "0.0.1"
@@ -6350,61 +6343,51 @@
},
"buffer-shims": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
- "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
+ "bundled": true
},
"caseless": {
"version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+ "bundled": true,
"optional": true
},
"co": {
"version": "4.6.0",
- "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "bundled": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
- "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+ "bundled": true
},
"combined-stream": {
"version": "1.0.5",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
- "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
+ "bundled": true,
"requires": {
"delayed-stream": "1.0.0"
}
},
"concat-map": {
"version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ "bundled": true
},
"console-control-strings": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+ "bundled": true
},
"core-util-is": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ "bundled": true
},
"cryptiles": {
"version": "2.0.5",
- "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
- "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+ "bundled": true,
"requires": {
"boom": "2.10.1"
}
},
"dashdash": {
"version": "1.14.1",
- "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
- "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "bundled": true,
"optional": true,
"requires": {
"assert-plus": "1.0.0"
@@ -6412,16 +6395,14 @@
"dependencies": {
"assert-plus": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "bundled": true,
"optional": true
}
}
},
"debug": {
"version": "2.6.8",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
- "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
+ "bundled": true,
"optional": true,
"requires": {
"ms": "2.0.0"
@@ -6429,31 +6410,26 @@
},
"deep-extend": {
"version": "0.4.2",
- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
- "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=",
+ "bundled": true,
"optional": true
},
"delayed-stream": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ "bundled": true
},
"delegates": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+ "bundled": true,
"optional": true
},
"detect-libc": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.2.tgz",
- "integrity": "sha1-ca1dIEvxempsqPRQxhRUBm70YeE=",
+ "bundled": true,
"optional": true
},
"ecc-jsbn": {
"version": "0.1.1",
- "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
- "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
+ "bundled": true,
"optional": true,
"requires": {
"jsbn": "0.1.1"
@@ -6461,25 +6437,21 @@
},
"extend": {
"version": "3.0.1",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
- "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=",
+ "bundled": true,
"optional": true
},
"extsprintf": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
- "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
+ "bundled": true
},
"forever-agent": {
"version": "0.6.1",
- "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
- "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+ "bundled": true,
"optional": true
},
"form-data": {
"version": "2.1.4",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
- "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+ "bundled": true,
"optional": true,
"requires": {
"asynckit": "0.4.0",
@@ -6489,13 +6461,11 @@
},
"fs.realpath": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ "bundled": true
},
"fstream": {
"version": "1.0.11",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
- "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "bundled": true,
"requires": {
"graceful-fs": "4.1.11",
"inherits": "2.0.3",
@@ -6505,8 +6475,7 @@
},
"fstream-ignore": {
"version": "1.0.5",
- "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
- "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
+ "bundled": true,
"optional": true,
"requires": {
"fstream": "1.0.11",
@@ -6516,8 +6485,7 @@
},
"gauge": {
"version": "2.7.4",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
- "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+ "bundled": true,
"optional": true,
"requires": {
"aproba": "1.1.1",
@@ -6532,8 +6500,7 @@
},
"getpass": {
"version": "0.1.7",
- "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
- "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "bundled": true,
"optional": true,
"requires": {
"assert-plus": "1.0.0"
@@ -6541,16 +6508,14 @@
"dependencies": {
"assert-plus": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "bundled": true,
"optional": true
}
}
},
"glob": {
"version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "bundled": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
@@ -6562,19 +6527,16 @@
},
"graceful-fs": {
"version": "4.1.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
- "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+ "bundled": true
},
"har-schema": {
"version": "1.0.5",
- "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
- "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+ "bundled": true,
"optional": true
},
"har-validator": {
"version": "4.2.1",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
- "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+ "bundled": true,
"optional": true,
"requires": {
"ajv": "4.11.8",
@@ -6583,14 +6545,12 @@
},
"has-unicode": {
"version": "2.0.1",
- "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+ "bundled": true,
"optional": true
},
"hawk": {
"version": "3.1.3",
- "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
- "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+ "bundled": true,
"requires": {
"boom": "2.10.1",
"cryptiles": "2.0.5",
@@ -6600,13 +6560,11 @@
},
"hoek": {
"version": "2.16.3",
- "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
- "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+ "bundled": true
},
"http-signature": {
"version": "1.1.1",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
- "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+ "bundled": true,
"optional": true,
"requires": {
"assert-plus": "0.2.0",
@@ -6616,8 +6574,7 @@
},
"inflight": {
"version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "bundled": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
@@ -6625,44 +6582,37 @@
},
"inherits": {
"version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ "bundled": true
},
"ini": {
"version": "1.3.4",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
- "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=",
+ "bundled": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "bundled": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"is-typedarray": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+ "bundled": true,
"optional": true
},
"isarray": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ "bundled": true
},
"isstream": {
"version": "0.1.2",
- "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
- "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+ "bundled": true,
"optional": true
},
"jodid25519": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
- "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=",
+ "bundled": true,
"optional": true,
"requires": {
"jsbn": "0.1.1"
@@ -6670,20 +6620,17 @@
},
"jsbn": {
"version": "0.1.1",
- "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
- "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "bundled": true,
"optional": true
},
"json-schema": {
"version": "0.2.3",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
- "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+ "bundled": true,
"optional": true
},
"json-stable-stringify": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
- "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+ "bundled": true,
"optional": true,
"requires": {
"jsonify": "0.0.0"
@@ -6691,20 +6638,17 @@
},
"json-stringify-safe": {
"version": "5.0.1",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+ "bundled": true,
"optional": true
},
"jsonify": {
"version": "0.0.0",
- "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
- "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+ "bundled": true,
"optional": true
},
"jsprim": {
"version": "1.4.0",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
- "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
+ "bundled": true,
"optional": true,
"requires": {
"assert-plus": "1.0.0",
@@ -6715,56 +6659,48 @@
"dependencies": {
"assert-plus": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "bundled": true,
"optional": true
}
}
},
"mime-db": {
"version": "1.27.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
- "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE="
+ "bundled": true
},
"mime-types": {
"version": "2.1.15",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
- "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=",
+ "bundled": true,
"requires": {
"mime-db": "1.27.0"
}
},
"minimatch": {
"version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "bundled": true,
"requires": {
"brace-expansion": "1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
- "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ "bundled": true
},
"mkdirp": {
"version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
- "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "bundled": true,
"requires": {
"minimist": "0.0.8"
}
},
"ms": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "bundled": true,
"optional": true
},
"node-pre-gyp": {
"version": "0.6.39",
- "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz",
- "integrity": "sha512-OsJV74qxnvz/AMGgcfZoDaeDXKD3oY3QVIbBmwszTFkRisTSXbMQyn4UWzUMOtA5SVhrBZOTp0wcoSBgfMfMmQ==",
+ "bundled": true,
"optional": true,
"requires": {
"detect-libc": "1.0.2",
@@ -6782,8 +6718,7 @@
},
"nopt": {
"version": "4.0.1",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
- "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+ "bundled": true,
"optional": true,
"requires": {
"abbrev": "1.1.0",
@@ -6792,8 +6727,7 @@
},
"npmlog": {
"version": "4.1.0",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz",
- "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==",
+ "bundled": true,
"optional": true,
"requires": {
"are-we-there-yet": "1.1.4",
@@ -6804,45 +6738,38 @@
},
"number-is-nan": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+ "bundled": true
},
"oauth-sign": {
"version": "0.8.2",
- "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
- "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+ "bundled": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "bundled": true,
"optional": true
},
"once": {
"version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "bundled": true,
"requires": {
"wrappy": "1.0.2"
}
},
"os-homedir": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
- "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+ "bundled": true,
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
- "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "bundled": true,
"optional": true
},
"osenv": {
"version": "0.1.4",
- "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz",
- "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=",
+ "bundled": true,
"optional": true,
"requires": {
"os-homedir": "1.0.2",
@@ -6851,36 +6778,30 @@
},
"path-is-absolute": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ "bundled": true
},
"performance-now": {
"version": "0.2.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
- "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
+ "bundled": true,
"optional": true
},
"process-nextick-args": {
"version": "1.0.7",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
- "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ "bundled": true
},
"punycode": {
"version": "1.4.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
- "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "bundled": true,
"optional": true
},
"qs": {
"version": "6.4.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
- "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
+ "bundled": true,
"optional": true
},
"rc": {
"version": "1.2.1",
- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz",
- "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=",
+ "bundled": true,
"optional": true,
"requires": {
"deep-extend": "0.4.2",
@@ -6891,16 +6812,14 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "bundled": true,
"optional": true
}
}
},
"readable-stream": {
"version": "2.2.9",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz",
- "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=",
+ "bundled": true,
"requires": {
"buffer-shims": "1.0.0",
"core-util-is": "1.0.2",
@@ -6913,8 +6832,7 @@
},
"request": {
"version": "2.81.0",
- "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
- "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+ "bundled": true,
"optional": true,
"requires": {
"aws-sign2": "0.6.0",
@@ -6943,47 +6861,40 @@
},
"rimraf": {
"version": "2.6.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
- "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
+ "bundled": true,
"requires": {
"glob": "7.1.2"
}
},
"safe-buffer": {
"version": "5.0.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
- "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
+ "bundled": true
},
"semver": {
"version": "5.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
- "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+ "bundled": true,
"optional": true
},
"set-blocking": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "bundled": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
- "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "bundled": true,
"optional": true
},
"sntp": {
"version": "1.0.9",
- "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
- "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+ "bundled": true,
"requires": {
"hoek": "2.16.3"
}
},
"sshpk": {
"version": "1.13.0",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz",
- "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=",
+ "bundled": true,
"optional": true,
"requires": {
"asn1": "0.2.3",
@@ -6999,16 +6910,14 @@
"dependencies": {
"assert-plus": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "bundled": true,
"optional": true
}
}
},
"string-width": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "bundled": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
@@ -7017,36 +6926,31 @@
},
"string_decoder": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz",
- "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=",
+ "bundled": true,
"requires": {
"safe-buffer": "5.0.1"
}
},
"stringstream": {
"version": "0.0.5",
- "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
- "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
+ "bundled": true,
"optional": true
},
"strip-ansi": {
"version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "bundled": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"strip-json-comments": {
"version": "2.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "bundled": true,
"optional": true
},
"tar": {
"version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
- "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+ "bundled": true,
"requires": {
"block-stream": "0.0.9",
"fstream": "1.0.11",
@@ -7055,8 +6959,7 @@
},
"tar-pack": {
"version": "3.4.0",
- "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz",
- "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=",
+ "bundled": true,
"optional": true,
"requires": {
"debug": "2.6.8",
@@ -7071,8 +6974,7 @@
},
"tough-cookie": {
"version": "2.3.2",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz",
- "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=",
+ "bundled": true,
"optional": true,
"requires": {
"punycode": "1.4.1"
@@ -7080,8 +6982,7 @@
},
"tunnel-agent": {
"version": "0.6.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "bundled": true,
"optional": true,
"requires": {
"safe-buffer": "5.0.1"
@@ -7089,31 +6990,26 @@
},
"tweetnacl": {
"version": "0.14.5",
- "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "bundled": true,
"optional": true
},
"uid-number": {
"version": "0.0.6",
- "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
- "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
+ "bundled": true,
"optional": true
},
"util-deprecate": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ "bundled": true
},
"uuid": {
"version": "3.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz",
- "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=",
+ "bundled": true,
"optional": true
},
"verror": {
"version": "1.3.6",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
- "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
+ "bundled": true,
"optional": true,
"requires": {
"extsprintf": "1.0.2"
@@ -7121,8 +7017,7 @@
},
"wide-align": {
"version": "1.1.2",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
- "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
+ "bundled": true,
"optional": true,
"requires": {
"string-width": "1.0.2"
@@ -7130,8 +7025,7 @@
},
"wrappy": {
"version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ "bundled": true
}
}
},
@@ -7602,9 +7496,9 @@
}
},
"glogg": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz",
- "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz",
+ "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==",
"requires": {
"sparkles": "1.0.0"
}
@@ -7675,13 +7569,13 @@
}
},
"gulp-babel": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-6.1.2.tgz",
- "integrity": "sha1-fAF25Lo/JExgWIoMSzIKRdGt784=",
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-6.1.3.tgz",
+ "integrity": "sha512-tm15R3rt4gO59WXCuqrwf4QXJM9VIJC+0J2NPYSC6xZn+cZRD5y5RPGAiHaDxCJq7Rz5BDljlrk3cEjWADF+wQ==",
"requires": {
"babel-core": "6.26.0",
- "gulp-util": "3.0.8",
"object-assign": "4.1.1",
+ "plugin-error": "1.0.1",
"replace-ext": "0.0.1",
"through2": "2.0.3",
"vinyl-sourcemaps-apply": "0.2.1"
@@ -7729,9 +7623,9 @@
"integrity": "sha1-OtRCh2PwXidk3sHGfYaNsnVoeBc="
},
"gulp-sourcemaps": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.12.0.tgz",
- "integrity": "sha1-eG+XyUoPloSSRl1wVY4EJCxnlZg=",
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.12.1.tgz",
+ "integrity": "sha1-tDfR89mAzyboEYSCNxjOFa5ll7Y=",
"requires": {
"@gulp-sourcemaps/map-sources": "1.0.0",
"acorn": "4.0.13",
@@ -7740,12 +7634,17 @@
"debug-fabulous": "0.0.4",
"detect-newline": "2.1.0",
"graceful-fs": "4.1.11",
- "source-map": "0.5.7",
+ "source-map": "0.6.1",
"strip-bom": "2.0.0",
"through2": "2.0.3",
"vinyl": "1.2.0"
},
"dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ },
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
@@ -8039,7 +7938,7 @@
"resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz",
"integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=",
"requires": {
- "glogg": "1.0.0"
+ "glogg": "1.0.1"
}
},
"gzip-size": {
@@ -8071,7 +7970,7 @@
"entities": "1.1.1",
"linkify-it": "2.0.3",
"mdurl": "1.0.1",
- "uc.micro": "1.0.3"
+ "uc.micro": "1.0.5"
}
}
}
@@ -8329,9 +8228,9 @@
}
},
"hooks-fixed": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.0.tgz",
- "integrity": "sha1-oB2JTVKsf2WZu7H2PfycQR33DLo="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-1.2.0.tgz",
+ "integrity": "sha1-DSdy1NfWhf+SRHJKnwtbJVmqyWs="
},
"hosted-git-info": {
"version": "2.5.0",
@@ -8361,7 +8260,7 @@
"ncname": "1.0.0",
"param-case": "2.1.1",
"relateurl": "0.2.7",
- "uglify-js": "3.3.8"
+ "uglify-js": "3.3.9"
},
"dependencies": {
"commander": {
@@ -8375,9 +8274,9 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"uglify-js": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.8.tgz",
- "integrity": "sha512-X0jAGtpSZRtd4RhbVNuGHyjZNa/h2MrVkKrR3Ew5iL2MJw6d7FmBke+fhVCALWySv1ygHnjjROG1KI1FAPvddw==",
+ "version": "3.3.9",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.9.tgz",
+ "integrity": "sha512-J2t8B5tj9JdPTW4+sNZXmiIWHzTvcoITkaqzTiilu/biZF/9crqf/Fi7k5hqbOmVRh9/hVNxAxBYIMF7N6SqMQ==",
"requires": {
"commander": "2.13.0",
"source-map": "0.6.1"
@@ -9139,9 +9038,9 @@
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
},
"in-app-purchase": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/in-app-purchase/-/in-app-purchase-1.8.5.tgz",
- "integrity": "sha1-OuvbB+qKyv/t9omePCfjVdgeZyo=",
+ "version": "1.8.7",
+ "resolved": "https://registry.npmjs.org/in-app-purchase/-/in-app-purchase-1.8.7.tgz",
+ "integrity": "sha1-TckQ5TQ3/1noPRzPA9RbhWMK4Sw=",
"requires": {
"request": "2.83.0",
"xml-crypto": "0.10.1",
@@ -9720,9 +9619,9 @@
}
},
"is-resolvable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.1.tgz",
- "integrity": "sha512-y5CXYbzvB3jTnWAZH1Nl7ykUWb6T3BcTs56HUruwBf8MhF56n1HWqhDWnVFo8GHrUPDgvUUNVhrc2U8W7iqz5g==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
+ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
"dev": true
},
"is-retry-allowed": {
@@ -10076,9 +9975,9 @@
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg=="
},
"js-base64": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.1.tgz",
- "integrity": "sha512-2h586r2I/CqU7z1aa1kBgWaVAXWAZK+zHnceGi/jFgn7+7VSluxYer/i3xOZVearCxxXvyDkLtTBo+OeJCA3kA=="
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz",
+ "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw=="
},
"js-stringify": {
"version": "1.0.2",
@@ -10246,7 +10145,7 @@
"socket.io": "1.7.3",
"source-map": "0.5.7",
"tmp": "0.0.31",
- "useragent": "2.2.1"
+ "useragent": "2.3.0"
},
"dependencies": {
"anymatch": {
@@ -11128,7 +11027,7 @@
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz",
"integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=",
"requires": {
- "uc.micro": "1.0.3"
+ "uc.micro": "1.0.5"
}
},
"load-json-file": {
@@ -11789,7 +11688,7 @@
"entities": "1.1.1",
"linkify-it": "2.0.3",
"mdurl": "1.0.1",
- "uc.micro": "1.0.3"
+ "uc.micro": "1.0.5"
}
},
"markdown-it-emoji": {
@@ -12192,16 +12091,6 @@
"readable-stream": "2.2.7"
},
"dependencies": {
- "mongodb-core": {
- "version": "2.1.18",
- "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.18.tgz",
- "integrity": "sha1-TEYTm986HwMt7ZHbSfOO7AFlkFA=",
- "dev": true,
- "requires": {
- "bson": "1.0.4",
- "require_optional": "1.0.1"
- }
- },
"readable-stream": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz",
@@ -12229,22 +12118,23 @@
}
},
"mongodb-core": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.8.tgz",
- "integrity": "sha1-sz4DcNClnZe2yx7GEFJ76elcosA=",
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.18.tgz",
+ "integrity": "sha1-TEYTm986HwMt7ZHbSfOO7AFlkFA=",
+ "dev": true,
"requires": {
"bson": "1.0.4",
"require_optional": "1.0.1"
}
},
"mongoose": {
- "version": "4.8.7",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.8.7.tgz",
- "integrity": "sha1-PGUOUcEj5TczuUeab8hXi+M+4G4=",
+ "version": "4.8.6",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.8.6.tgz",
+ "integrity": "sha1-8bN2skP+hvVNXVzdx7mQqt6Fjw0=",
"requires": {
"async": "2.1.4",
"bson": "1.0.4",
- "hooks-fixed": "2.0.0",
+ "hooks-fixed": "1.2.0",
"kareem": "1.2.1",
"mongodb": "2.2.24",
"mpath": "0.2.1",
@@ -12274,6 +12164,15 @@
"readable-stream": "2.1.5"
}
},
+ "mongodb-core": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.8.tgz",
+ "integrity": "sha1-sz4DcNClnZe2yx7GEFJ76elcosA=",
+ "requires": {
+ "bson": "1.0.4",
+ "require_optional": "1.0.1"
+ }
+ },
"ms": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
@@ -12300,7 +12199,7 @@
"resolved": "https://registry.npmjs.org/mongoose-id-autoinc/-/mongoose-id-autoinc-2013.7.14-4.tgz",
"integrity": "sha1-edd2NKYOl+gyHq88WQpKR/WgRl0=",
"requires": {
- "mongoose": "4.8.7"
+ "mongoose": "4.8.6"
}
},
"mongoskin": {
@@ -12820,7 +12719,7 @@
"console-browserify": "1.1.0",
"constants-browserify": "1.0.0",
"crypto-browserify": "3.12.0",
- "domain-browser": "1.1.7",
+ "domain-browser": "1.2.0",
"events": "1.1.1",
"https-browserify": "1.0.0",
"os-browserify": "0.3.0",
@@ -12832,7 +12731,7 @@
"stream-browserify": "2.0.1",
"stream-http": "2.8.0",
"string_decoder": "1.0.3",
- "timers-browserify": "2.0.4",
+ "timers-browserify": "2.0.6",
"tty-browserify": "0.0.0",
"url": "0.11.0",
"util": "0.10.3",
@@ -13866,7 +13765,7 @@
"integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
"requires": {
"got": "6.7.1",
- "registry-auth-token": "3.3.1",
+ "registry-auth-token": "3.3.2",
"registry-url": "3.1.0",
"semver": "5.5.0"
},
@@ -14207,7 +14106,7 @@
"create-hmac": "1.1.6",
"ripemd160": "2.0.1",
"safe-buffer": "5.1.1",
- "sha.js": "2.4.9"
+ "sha.js": "2.4.10"
}
},
"pend": {
@@ -14233,7 +14132,7 @@
"resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz",
"integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=",
"requires": {
- "es6-promise": "4.2.2",
+ "es6-promise": "4.2.4",
"extract-zip": "1.6.6",
"fs-extra": "1.0.0",
"hasha": "2.2.0",
@@ -14286,9 +14185,9 @@
}
},
"es6-promise": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.2.tgz",
- "integrity": "sha512-LSas5vsuA6Q4nEdf9wokY5/AJYXry98i0IzXsv49rYsgDGDNDPbqAYR1Pe23iFxygfbGZNR/5VrHXBCh2BhvUQ=="
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
+ "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ=="
},
"form-data": {
"version": "2.3.1",
@@ -14488,6 +14387,36 @@
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz",
"integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8="
},
+ "plugin-error": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
+ "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==",
+ "requires": {
+ "ansi-colors": "1.0.1",
+ "arr-diff": "4.0.0",
+ "arr-union": "3.1.0",
+ "extend-shallow": "3.0.2"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "requires": {
+ "assign-symbols": "1.0.0",
+ "is-extendable": "1.0.1"
+ }
+ },
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "requires": {
+ "is-plain-object": "2.0.4"
+ }
+ }
+ }
+ },
"plur": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz",
@@ -14523,7 +14452,7 @@
"integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==",
"requires": {
"chalk": "1.1.3",
- "js-base64": "2.4.1",
+ "js-base64": "2.4.3",
"source-map": "0.5.7",
"supports-color": "3.2.3"
},
@@ -15849,9 +15778,9 @@
}
},
"registry-auth-token": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz",
- "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
+ "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
"requires": {
"rc": "1.2.4",
"safe-buffer": "5.1.1"
@@ -16308,7 +16237,7 @@
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
"integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
"requires": {
- "js-base64": "2.4.1",
+ "js-base64": "2.4.3",
"source-map": "0.4.4"
},
"dependencies": {
@@ -16395,7 +16324,7 @@
"debug": "2.2.0",
"depd": "1.1.2",
"destroy": "1.0.4",
- "encodeurl": "1.0.1",
+ "encodeurl": "1.0.2",
"escape-html": "1.0.3",
"etag": "1.7.0",
"fresh": "0.3.0",
@@ -16464,7 +16393,7 @@
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.11.2.tgz",
"integrity": "sha1-LPmIm9RDWjIMw2iVyapXvWYuasc=",
"requires": {
- "encodeurl": "1.0.1",
+ "encodeurl": "1.0.2",
"escape-html": "1.0.3",
"parseurl": "1.3.2",
"send": "0.14.2"
@@ -16510,9 +16439,9 @@
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
},
"sha.js": {
- "version": "2.4.9",
- "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz",
- "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==",
+ "version": "2.4.10",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.10.tgz",
+ "integrity": "sha512-vnwmrFDlOExK4Nm16J2KMWHLrp14lBrjxMxBJpu++EnsuBmpiYaM/MEs46Vxxm/4FvdP5yTwuCTO9it5FSjrqA==",
"requires": {
"inherits": "2.0.3",
"safe-buffer": "5.1.1"
@@ -18057,9 +17986,9 @@
"integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
},
"timers-browserify": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz",
- "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz",
+ "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==",
"requires": {
"setimmediate": "1.0.5"
}
@@ -18380,9 +18309,9 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"uc.micro": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.3.tgz",
- "integrity": "sha1-ftUNXg+an7ClczeSWfKndFjVAZI="
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz",
+ "integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg=="
},
"uglify-js": {
"version": "2.6.4",
@@ -18818,19 +18747,12 @@
"integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA="
},
"useragent": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.2.1.tgz",
- "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
+ "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
"requires": {
- "lru-cache": "2.2.4",
+ "lru-cache": "4.1.1",
"tmp": "0.0.33"
- },
- "dependencies": {
- "lru-cache": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz",
- "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0="
- }
}
},
"util": {
@@ -19072,7 +18994,7 @@
"resolve": "1.5.0",
"source-map": "0.6.1",
"vue-hot-reload-api": "2.2.4",
- "vue-style-loader": "3.0.3",
+ "vue-style-loader": "3.1.1",
"vue-template-es2015-compiler": "1.6.0"
},
"dependencies": {
@@ -19158,9 +19080,9 @@
"integrity": "sha512-vLLoY452L+JBpALMP5UHum9+7nzR9PeIBCghU9ZtJ1eWm6ieUI8Zb/DI3MYxH32bxkjzYV1LRjNv4qr8d+uX/w=="
},
"vue-style-loader": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.0.3.tgz",
- "integrity": "sha512-P/ihpaZKU23T1kq3E0y4c+F8sbm1HQO69EFYoLoGMSGVAHroHsGir/WQ9qUavP8dyFYHmXenzHaJ/nqd8vfaxw==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-3.1.1.tgz",
+ "integrity": "sha512-AX0tRYm5sQUn2G1gYwyFhreU5/2+oFEf0ddsiF1SyRv6bycY1ntqNu7r3CF92KxTXbmRzfm9bVz3Ys/xm+kQQg==",
"requires": {
"hash-sum": "1.0.2",
"loader-utils": "1.1.0"
@@ -19540,7 +19462,7 @@
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "1.1.2",
- "encodeurl": "1.0.1",
+ "encodeurl": "1.0.2",
"escape-html": "1.0.3",
"etag": "1.8.1",
"finalhandler": "1.1.0",
@@ -19570,7 +19492,7 @@
"dev": true,
"requires": {
"debug": "2.6.9",
- "encodeurl": "1.0.1",
+ "encodeurl": "1.0.2",
"escape-html": "1.0.3",
"on-finished": "2.3.0",
"parseurl": "1.3.2",
@@ -19621,7 +19543,7 @@
"debug": "2.6.9",
"depd": "1.1.2",
"destroy": "1.0.4",
- "encodeurl": "1.0.1",
+ "encodeurl": "1.0.2",
"escape-html": "1.0.3",
"etag": "1.8.1",
"fresh": "0.5.2",
@@ -19639,7 +19561,7 @@
"integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==",
"dev": true,
"requires": {
- "encodeurl": "1.0.1",
+ "encodeurl": "1.0.2",
"escape-html": "1.0.3",
"parseurl": "1.3.2",
"send": "0.16.1"
diff --git a/package.json b/package.json
index 17460cc059..d4409f4476 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,7 @@
"method-override": "^2.3.5",
"moment": "^2.13.0",
"moment-recur": "git://github.com/habitrpg/moment-recur.git#f147ef27bbc26ca67638385f3db4a44084c76626",
- "mongoose": "~4.8.6",
+ "mongoose": "^4.8.6",
"mongoose-id-autoinc": "~2013.7.14-4",
"morgan": "^1.7.0",
"nconf": "~0.8.2",
diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js
index 9255772ad2..fd9569bc8c 100644
--- a/test/api/v3/integration/chat/POST-chat.test.js
+++ b/test/api/v3/integration/chat/POST-chat.test.js
@@ -426,6 +426,9 @@ describe('POST /chat', () => {
expect(message.message.id).to.exist;
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist;
+ expect(memberWithNotification.notifications.find(n => {
+ return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupWithChat._id;
+ })).to.exist;
});
it('notifies other users of new messages for a party', async () => {
@@ -443,6 +446,9 @@ describe('POST /chat', () => {
expect(message.message.id).to.exist;
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
+ expect(memberWithNotification.notifications.find(n => {
+ return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === group._id;
+ })).to.exist;
});
context('Spam prevention', () => {
diff --git a/test/api/v3/integration/chat/POST-chat_seen.test.js b/test/api/v3/integration/chat/POST-chat_seen.test.js
index 8b22461a04..9207f58cf5 100644
--- a/test/api/v3/integration/chat/POST-chat_seen.test.js
+++ b/test/api/v3/integration/chat/POST-chat_seen.test.js
@@ -24,10 +24,13 @@ describe('POST /groups/:id/chat/seen', () => {
});
it('clears new messages for a guild', async () => {
+ await guildMember.sync();
+ const initialNotifications = guildMember.notifications.length;
await guildMember.post(`/groups/${guild._id}/chat/seen`);
let guildThatHasSeenChat = await guildMember.get('/user');
+ expect(guildThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
expect(guildThatHasSeenChat.newMessages).to.be.empty;
});
});
@@ -53,10 +56,13 @@ describe('POST /groups/:id/chat/seen', () => {
});
it('clears new messages for a party', async () => {
+ await partyMember.sync();
+ const initialNotifications = partyMember.notifications.length;
await partyMember.post(`/groups/${party._id}/chat/seen`);
let partyMemberThatHasSeenChat = await partyMember.get('/user');
+ expect(partyMemberThatHasSeenChat.notifications.length).to.equal(initialNotifications - 1);
expect(partyMemberThatHasSeenChat.newMessages).to.be.empty;
});
});
diff --git a/test/api/v3/integration/groups/POST-groups_groupId_leave.js b/test/api/v3/integration/groups/POST-groups_groupId_leave.js
index 403baf5da7..0ff58aee32 100644
--- a/test/api/v3/integration/groups/POST-groups_groupId_leave.js
+++ b/test/api/v3/integration/groups/POST-groups_groupId_leave.js
@@ -70,13 +70,21 @@ describe('POST /groups/:groupId/leave', () => {
it('removes new messages for that group from user', async () => {
await member.post(`/groups/${groupToLeave._id}/chat`, { message: 'Some message' });
+ await sleep(0.5);
+
await leader.sync();
+ expect(leader.notifications.find(n => {
+ return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
+ })).to.exist;
expect(leader.newMessages[groupToLeave._id]).to.not.be.empty;
await leader.post(`/groups/${groupToLeave._id}/leave`);
await leader.sync();
+ expect(leader.notifications.find(n => {
+ return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupToLeave._id;
+ })).to.not.exist;
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
});
diff --git a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js
index e846a59290..f0cfd31ceb 100644
--- a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js
+++ b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js
@@ -2,6 +2,7 @@ import {
generateUser,
createAndPopulateGroup,
translate as t,
+ sleep,
} from '../../../../helpers/api-v3-integration.helper';
import * as email from '../../../../../website/server/libs/email';
@@ -188,13 +189,20 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
it('removes new messages from a member who is removed', async () => {
await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some message' });
+ await sleep(0.5);
await removedMember.sync();
+ expect(removedMember.notifications.find(n => {
+ return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
+ })).to.exist;
expect(removedMember.newMessages[party._id]).to.not.be.empty;
await partyLeader.post(`/groups/${party._id}/removeMember/${removedMember._id}`);
await removedMember.sync();
+ expect(removedMember.notifications.find(n => {
+ return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === party._id;
+ })).to.not.exist;
expect(removedMember.newMessages[party._id]).to.be.empty;
});
diff --git a/test/api/v3/integration/groups/POST-groups_invite.test.js b/test/api/v3/integration/groups/POST-groups_invite.test.js
index debe0d122f..3de01d0d40 100644
--- a/test/api/v3/integration/groups/POST-groups_invite.test.js
+++ b/test/api/v3/integration/groups/POST-groups_invite.test.js
@@ -110,6 +110,7 @@ describe('Post /groups/:groupId/invite', () => {
id: group._id,
name: groupName,
inviter: inviter._id,
+ publicGuild: false,
}]);
await expect(userToInvite.get('/user'))
@@ -127,11 +128,13 @@ describe('Post /groups/:groupId/invite', () => {
id: group._id,
name: groupName,
inviter: inviter._id,
+ publicGuild: false,
},
{
id: group._id,
name: groupName,
inviter: inviter._id,
+ publicGuild: false,
},
]);
diff --git a/test/api/v3/integration/members/POST-send_private_message.test.js b/test/api/v3/integration/members/POST-send_private_message.test.js
index aaa3cb3a2c..0e484dc68f 100644
--- a/test/api/v3/integration/members/POST-send_private_message.test.js
+++ b/test/api/v3/integration/members/POST-send_private_message.test.js
@@ -98,6 +98,7 @@ describe('POST /members/send-private-message', () => {
it('sends a private message to a user', async () => {
let receiver = await generateUser();
+ // const initialNotifications = receiver.notifications.length;
await userToSendMessage.post('/members/send-private-message', {
message: messageToSend,
@@ -115,10 +116,44 @@ describe('POST /members/send-private-message', () => {
return message.uuid === receiver._id && message.text === messageToSend;
});
+ // @TODO waiting for mobile support
+ // expect(updatedReceiver.notifications.length).to.equal(initialNotifications + 1);
+ // const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
+
+ // expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
+ // expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
+ // expect(notification.data.excerpt).to.equal(messageToSend);
+ // expect(notification.data.sender.id).to.equal(updatedSender._id);
+ // expect(notification.data.sender.name).to.equal(updatedSender.profile.name);
+
expect(sendersMessageInReceiversInbox).to.exist;
expect(sendersMessageInSendersInbox).to.exist;
});
+ // @TODO waiting for mobile support
+ xit('creates a notification with an excerpt if the message is too long', async () => {
+ let receiver = await generateUser();
+ let longerMessageToSend = 'A very long message, that for sure exceeds the limit of 100 chars for the excerpt that we set to 100 chars';
+ let messageExcerpt = `${longerMessageToSend.substring(0, 100)}...`;
+
+ await userToSendMessage.post('/members/send-private-message', {
+ message: longerMessageToSend,
+ toUserId: receiver._id,
+ });
+
+ let updatedReceiver = await receiver.get('/user');
+
+ let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
+ return message.uuid === userToSendMessage._id && message.text === longerMessageToSend;
+ });
+
+ const notification = updatedReceiver.notifications[updatedReceiver.notifications.length - 1];
+
+ expect(notification.type).to.equal('NEW_INBOX_MESSAGE');
+ expect(notification.data.messageId).to.equal(sendersMessageInReceiversInbox.id);
+ expect(notification.data.excerpt).to.equal(messageExcerpt);
+ });
+
it('allows admin to send when sender has blocked the admin', async () => {
userToSendMessage = await generateUser({
'contributor.admin': 1,
diff --git a/test/api/v3/integration/news/GET-news.test.js b/test/api/v3/integration/news/GET-news.test.js
new file mode 100644
index 0000000000..b09d408706
--- /dev/null
+++ b/test/api/v3/integration/news/GET-news.test.js
@@ -0,0 +1,16 @@
+import {
+ requester,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('GET /news', () => {
+ let api;
+
+ beforeEach(async () => {
+ api = requester();
+ });
+
+ it('returns the latest news in html format, does not require authentication', async () => {
+ const res = await api.get('/news');
+ expect(res).to.be.a.string;
+ });
+});
diff --git a/test/api/v3/integration/news/POST-news_tell_me_later.test.js b/test/api/v3/integration/news/POST-news_tell_me_later.test.js
new file mode 100644
index 0000000000..47444574c4
--- /dev/null
+++ b/test/api/v3/integration/news/POST-news_tell_me_later.test.js
@@ -0,0 +1,42 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /news/tell-me-later', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'flags.newStuff': true,
+ });
+ });
+
+ it('marks new stuff as read and adds notification', async () => {
+ expect(user.flags.newStuff).to.equal(true);
+ const initialNotifications = user.notifications.length;
+
+ await user.post('/news/tell-me-later');
+ await user.sync();
+
+ expect(user.flags.newStuff).to.equal(false);
+ expect(user.notifications.length).to.equal(initialNotifications + 1);
+
+ const notification = user.notifications[user.notifications.length - 1];
+
+ expect(notification.type).to.equal('NEW_STUFF');
+ // should be marked as seen by default so it's not counted in the number of notifications
+ expect(notification.seen).to.equal(true);
+ expect(notification.data.title).to.be.a.string;
+ });
+
+ it('never adds two notifications', async () => {
+ const initialNotifications = user.notifications.length;
+
+ await user.post('/news/tell-me-later');
+ await user.post('/news/tell-me-later');
+
+ await user.sync();
+
+ expect(user.notifications.length).to.equal(initialNotifications + 1);
+ });
+});
diff --git a/test/api/v3/integration/notifications/POST-notifications_notificationId_read.test.js b/test/api/v3/integration/notifications/POST-notifications_notificationId_read.test.js
index 02d3fd04e4..ea353aa32b 100644
--- a/test/api/v3/integration/notifications/POST-notifications_notificationId_read.test.js
+++ b/test/api/v3/integration/notifications/POST-notifications_notificationId_read.test.js
@@ -47,6 +47,7 @@ describe('POST /notifications/:notificationId/read', () => {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
+ seen: false,
}]);
await user.sync();
diff --git a/test/api/v3/integration/notifications/POST-notifications_notificationId_see.test.js b/test/api/v3/integration/notifications/POST-notifications_notificationId_see.test.js
new file mode 100644
index 0000000000..0b448f10fd
--- /dev/null
+++ b/test/api/v3/integration/notifications/POST-notifications_notificationId_see.test.js
@@ -0,0 +1,59 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /notifications/:notificationId/see', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('errors when notification is not found', async () => {
+ let dummyId = generateUUID();
+
+ await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageNotificationNotFound'),
+ });
+ });
+
+ it('mark a notification as seen', async () => {
+ expect(user.notifications.length).to.equal(0);
+
+ const id = generateUUID();
+ const id2 = generateUUID();
+
+ await user.update({
+ notifications: [{
+ id,
+ type: 'DROPS_ENABLED',
+ data: {},
+ }, {
+ id: id2,
+ type: 'LOGIN_INCENTIVE',
+ data: {},
+ }],
+ });
+
+ const userObj = await user.get('/user'); // so we can check that defaults have been applied
+ expect(userObj.notifications.length).to.equal(2);
+ expect(userObj.notifications[0].seen).to.equal(false);
+
+ const res = await user.post(`/notifications/${id}/see`);
+ expect(res).to.deep.equal({
+ id,
+ type: 'DROPS_ENABLED',
+ data: {},
+ seen: true,
+ });
+
+ await user.sync();
+ expect(user.notifications.length).to.equal(2);
+ expect(user.notifications[0].id).to.equal(id);
+ expect(user.notifications[0].seen).to.equal(true);
+ });
+});
diff --git a/test/api/v3/integration/notifications/POST-notifications_read.test.js b/test/api/v3/integration/notifications/POST-notifications_read.test.js
index 80061e220a..55a9d3350b 100644
--- a/test/api/v3/integration/notifications/POST-notifications_read.test.js
+++ b/test/api/v3/integration/notifications/POST-notifications_read.test.js
@@ -4,7 +4,7 @@ import {
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
-describe('POST /notifications/:notificationId/read', () => {
+describe('POST /notifications/read', () => {
let user;
before(async () => {
@@ -57,6 +57,7 @@ describe('POST /notifications/:notificationId/read', () => {
id: id2,
type: 'LOGIN_INCENTIVE',
data: {},
+ seen: false,
}]);
await user.sync();
diff --git a/test/api/v3/integration/notifications/POST_notifications_see.test.js b/test/api/v3/integration/notifications/POST_notifications_see.test.js
new file mode 100644
index 0000000000..d09d9bde07
--- /dev/null
+++ b/test/api/v3/integration/notifications/POST_notifications_see.test.js
@@ -0,0 +1,88 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /notifications/see', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('errors when notification is not found', async () => {
+ let dummyId = generateUUID();
+
+ await expect(user.post('/notifications/see', {
+ notificationIds: [dummyId],
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageNotificationNotFound'),
+ });
+ });
+
+ it('mark multiple notifications as seen', async () => {
+ expect(user.notifications.length).to.equal(0);
+
+ const id = generateUUID();
+ const id2 = generateUUID();
+ const id3 = generateUUID();
+
+ await user.update({
+ notifications: [{
+ id,
+ type: 'DROPS_ENABLED',
+ data: {},
+ seen: false,
+ }, {
+ id: id2,
+ type: 'LOGIN_INCENTIVE',
+ data: {},
+ seen: false,
+ }, {
+ id: id3,
+ type: 'CRON',
+ data: {},
+ seen: false,
+ }],
+ });
+
+ await user.sync();
+ expect(user.notifications.length).to.equal(3);
+
+ const res = await user.post('/notifications/see', {
+ notificationIds: [id, id3],
+ });
+
+ expect(res).to.deep.equal([
+ {
+ id,
+ type: 'DROPS_ENABLED',
+ data: {},
+ seen: true,
+ }, {
+ id: id2,
+ type: 'LOGIN_INCENTIVE',
+ data: {},
+ seen: false,
+ }, {
+ id: id3,
+ type: 'CRON',
+ data: {},
+ seen: true,
+ }]);
+
+ await user.sync();
+ expect(user.notifications.length).to.equal(3);
+ expect(user.notifications[0].id).to.equal(id);
+ expect(user.notifications[0].seen).to.equal(true);
+
+ expect(user.notifications[1].id).to.equal(id2);
+ expect(user.notifications[1].seen).to.equal(false);
+
+ expect(user.notifications[2].id).to.equal(id3);
+ expect(user.notifications[2].seen).to.equal(true);
+ });
+});
diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js
new file mode 100644
index 0000000000..0be0beb720
--- /dev/null
+++ b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_needs-work_userId.test.js
@@ -0,0 +1,189 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { find } from 'lodash';
+
+describe('POST /tasks/:id/needs-work/:userId', () => {
+ let user, guild, member, member2, task;
+
+ function findAssignedTask (memberTask) {
+ return memberTask.group.id === guild._id;
+ }
+
+ beforeEach(async () => {
+ let {group, members, groupLeader} = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Guild',
+ type: 'guild',
+ },
+ members: 2,
+ });
+
+ guild = group;
+ user = groupLeader;
+ member = members[0];
+ member2 = members[1];
+
+ task = await user.post(`/tasks/group/${guild._id}`, {
+ text: 'test todo',
+ type: 'todo',
+ requiresApproval: true,
+ });
+ });
+
+ it('errors when user is not assigned', async () => {
+ await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('errors when user is not the group leader', async () => {
+ await user.post(`/tasks/${task._id}/assign/${member._id}`);
+ await expect(member.post(`/tasks/${task._id}/needs-work/${member._id}`))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyGroupLeaderCanEditTasks'),
+ });
+ });
+
+ it('marks as task as needing more work', async () => {
+ const initialNotifications = member.notifications.length;
+
+ await user.post(`/tasks/${task._id}/assign/${member._id}`);
+
+ let memberTasks = await member.get('/tasks/user');
+ let syncedTask = find(memberTasks, findAssignedTask);
+
+ // score task to require approval
+ await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('taskApprovalHasBeenRequested'),
+ });
+
+ await user.post(`/tasks/${task._id}/needs-work/${member._id}`);
+
+ [memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
+ syncedTask = find(memberTasks, findAssignedTask);
+
+ // Check that the notification approval request has been removed
+ expect(syncedTask.group.approval.requested).to.equal(false);
+ expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
+
+ // Check that the notification is correct
+ expect(member.notifications.length).to.equal(initialNotifications + 1);
+ const notification = member.notifications[member.notifications.length - 1];
+ expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
+
+ const taskText = syncedTask.text;
+ const managerName = user.profile.name;
+
+ expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName}));
+
+ expect(notification.data.task.id).to.equal(syncedTask._id);
+ expect(notification.data.task.text).to.equal(taskText);
+
+ expect(notification.data.group.id).to.equal(syncedTask.group.id);
+ expect(notification.data.group.name).to.equal(guild.name);
+
+ expect(notification.data.manager.id).to.equal(user._id);
+ expect(notification.data.manager.name).to.equal(managerName);
+
+ // Check that the managers' GROUP_TASK_APPROVAL notifications have been removed
+ await user.sync();
+
+ expect(user.notifications.find(n => {
+ n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
+ })).to.equal(undefined);
+ });
+
+ it('allows a manager to mark a task as needing work', async () => {
+ await user.post(`/groups/${guild._id}/add-manager`, {
+ managerId: member2._id,
+ });
+ await member2.post(`/tasks/${task._id}/assign/${member._id}`);
+
+ let memberTasks = await member.get('/tasks/user');
+ let syncedTask = find(memberTasks, findAssignedTask);
+
+ // score task to require approval
+ await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('taskApprovalHasBeenRequested'),
+ });
+
+ const initialNotifications = member.notifications.length;
+
+ await member2.post(`/tasks/${task._id}/needs-work/${member._id}`);
+
+ [memberTasks] = await Promise.all([member.get('/tasks/user'), member.sync()]);
+ syncedTask = find(memberTasks, findAssignedTask);
+
+ // Check that the notification approval request has been removed
+ expect(syncedTask.group.approval.requested).to.equal(false);
+ expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
+
+ expect(member.notifications.length).to.equal(initialNotifications + 1);
+ const notification = member.notifications[member.notifications.length - 1];
+ expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
+
+ const taskText = syncedTask.text;
+ const managerName = member2.profile.name;
+
+ expect(notification.data.message).to.equal(t('taskNeedsWork', {taskText, managerName}));
+
+ expect(notification.data.task.id).to.equal(syncedTask._id);
+ expect(notification.data.task.text).to.equal(taskText);
+
+ expect(notification.data.group.id).to.equal(syncedTask.group.id);
+ expect(notification.data.group.name).to.equal(guild.name);
+
+ expect(notification.data.manager.id).to.equal(member2._id);
+ expect(notification.data.manager.name).to.equal(managerName);
+
+ // Check that the managers' GROUP_TASK_APPROVAL notifications have been removed
+ await Promise.all([user.sync(), member2.sync()]);
+
+ expect(user.notifications.find(n => {
+ n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
+ })).to.equal(undefined);
+
+ expect(member2.notifications.find(n => {
+ n.data.taskId === syncedTask._id && n.type === 'GROUP_TASK_APPROVAL';
+ })).to.equal(undefined);
+ });
+
+ it('prevents marking a task as needing work if it was already approved', async () => {
+ await user.post(`/groups/${guild._id}/add-manager`, {
+ managerId: member2._id,
+ });
+
+ await member2.post(`/tasks/${task._id}/assign/${member._id}`);
+ await member2.post(`/tasks/${task._id}/approve/${member._id}`);
+ await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('canOnlyApproveTaskOnce'),
+ });
+ });
+
+ it('prevents marking a task as needing work if it is not waiting for approval', async () => {
+ await user.post(`/tasks/${task._id}/assign/${member._id}`);
+
+ await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('taskApprovalWasNotRequested'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/GET-user_anonymized.test.js b/test/api/v3/integration/user/GET-user_anonymized.test.js
index 26c8525284..dd1c9c5723 100644
--- a/test/api/v3/integration/user/GET-user_anonymized.test.js
+++ b/test/api/v3/integration/user/GET-user_anonymized.test.js
@@ -25,6 +25,7 @@ describe('GET /user/anonymized', () => {
'achievements.challenges': 'some',
'inbox.messages': [{ text: 'some text' }],
tags: [{ name: 'some name', challenge: 'some challenge' }],
+ notifications: [],
});
await generateHabit({ userId: user._id });
@@ -65,6 +66,7 @@ describe('GET /user/anonymized', () => {
expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl));
expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30?
expect(returnedUser.newMessages).to.not.exist;
+ expect(returnedUser.notifications).to.not.exist;
expect(returnedUser.profile).to.not.exist;
expect(returnedUser.purchased.plan).to.not.exist;
expect(returnedUser.contributor).to.not.exist;
diff --git a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js
index 170a7e09ca..40ac6eca24 100644
--- a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js
+++ b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js
@@ -13,15 +13,20 @@ describe('POST /user/open-mystery-item', () => {
beforeEach(async () => {
user = await generateUser({
'purchased.plan.mysteryItems': [mysteryItemKey],
+ notifications: [
+ {type: 'NEW_MYSTERY_ITEMS', data: { items: [mysteryItemKey] }},
+ ],
});
});
// More tests in common code unit tests
it('opens a mystery item', async () => {
+ expect(user.notifications.length).to.equal(1);
let response = await user.post('/user/open-mystery-item');
await user.sync();
+ expect(user.notifications.length).to.equal(0);
expect(user.items.gear.owned[mysteryItemKey]).to.be.true;
expect(response.message).to.equal(t('mysteryItemOpened'));
expect(response.data.key).to.eql(mysteryItemKey);
diff --git a/test/api/v3/integration/user/POST-user_read_card.test.js b/test/api/v3/integration/user/POST-user_read_card.test.js
index 3b3573b6cc..bb8f388db2 100644
--- a/test/api/v3/integration/user/POST-user_read_card.test.js
+++ b/test/api/v3/integration/user/POST-user_read_card.test.js
@@ -26,13 +26,21 @@ describe('POST /user/read-card/:cardType', () => {
await user.update({
'items.special.greetingReceived': [true],
'flags.cardReceived': true,
+ notifications: [{
+ type: 'CARD_RECEIVED',
+ data: {card: cardType},
+ }],
});
+ await user.sync();
+ expect(user.notifications.length).to.equal(1);
+
let response = await user.post(`/user/read-card/${cardType}`);
await user.sync();
expect(response.message).to.equal(t('readCard', {cardType}));
expect(user.items.special[`${cardType}Received`]).to.be.empty;
expect(user.flags.cardReceived).to.be.false;
+ expect(user.notifications.length).to.equal(0);
});
});
diff --git a/test/api/v3/unit/libs/payments.test.js b/test/api/v3/unit/libs/payments.test.js
index f3e03758db..54e121c7b7 100644
--- a/test/api/v3/unit/libs/payments.test.js
+++ b/test/api/v3/unit/libs/payments.test.js
@@ -420,11 +420,16 @@ describe('payments/index', () => {
data = { paymentMethod: 'PaymentMethod', user, sub: { key: 'basic_3mo' } };
+ const oldNotificationsCount = user.notifications.length;
+
await api.createSubscription(data);
+ expect(user.notifications.find(n => n.type === 'NEW_MYSTERY_ITEMS')).to.not.be.undefined;
expect(user.purchased.plan.mysteryItems).to.have.a.lengthOf(2);
expect(user.purchased.plan.mysteryItems).to.include('armor_mystery_201605');
expect(user.purchased.plan.mysteryItems).to.include('head_mystery_201605');
+ expect(user.notifications.length).to.equal(oldNotificationsCount + 1);
+ expect(user.notifications[0].type).to.equal('NEW_MYSTERY_ITEMS');
fakeClock.restore();
});
diff --git a/test/api/v3/unit/middlewares/response.js b/test/api/v3/unit/middlewares/response.js
index d612b1912b..71a8bf3a22 100644
--- a/test/api/v3/unit/middlewares/response.js
+++ b/test/api/v3/unit/middlewares/response.js
@@ -106,6 +106,7 @@ describe('response middleware', () => {
type: notification.type,
id: notification.id,
data: {},
+ seen: false,
},
],
userV: res.locals.user._v,
diff --git a/test/api/v3/unit/models/group.test.js b/test/api/v3/unit/models/group.test.js
index db44574a8b..18cdb23b8b 100644
--- a/test/api/v3/unit/models/group.test.js
+++ b/test/api/v3/unit/models/group.test.js
@@ -1011,13 +1011,6 @@ describe('Group Model', () => {
expect(User.update).to.be.calledWithMatch({
'party._id': party._id,
_id: { $ne: '' },
- }, {
- $set: {
- [`newMessages.${party._id}`]: {
- name: party.name,
- value: true,
- },
- },
});
});
@@ -1032,13 +1025,6 @@ describe('Group Model', () => {
expect(User.update).to.be.calledWithMatch({
guilds: group._id,
_id: { $ne: '' },
- }, {
- $set: {
- [`newMessages.${group._id}`]: {
- name: group.name,
- value: true,
- },
- },
});
});
@@ -1049,13 +1035,6 @@ describe('Group Model', () => {
expect(User.update).to.be.calledWithMatch({
'party._id': party._id,
_id: { $ne: 'user-id' },
- }, {
- $set: {
- [`newMessages.${party._id}`]: {
- name: party.name,
- value: true,
- },
- },
});
});
diff --git a/test/api/v3/unit/models/user.test.js b/test/api/v3/unit/models/user.test.js
index 6be822fc8d..4a7dd00b49 100644
--- a/test/api/v3/unit/models/user.test.js
+++ b/test/api/v3/unit/models/user.test.js
@@ -58,21 +58,23 @@ describe('User Model', () => {
let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
- expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
+ expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({});
+ expect(userToJSON.notifications[0].seen).to.eql(false);
});
- it('can add notifications with data', () => {
+ it('can add notifications with data and already marked as seen', () => {
let user = new User();
- user.addNotification('CRON', {field: 1});
+ user.addNotification('CRON', {field: 1}, true);
let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
- expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
+ expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({field: 1});
+ expect(userToJSON.notifications[0].seen).to.eql(true);
});
context('static push method', () => {
@@ -86,7 +88,7 @@ describe('User Model', () => {
let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
- expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
+ expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({});
});
@@ -96,6 +98,7 @@ describe('User Model', () => {
await user.save();
expect(User.pushNotification({_id: user._id}, 'BAD_TYPE')).to.eventually.be.rejected;
+ expect(User.pushNotification({_id: user._id}, 'CRON', null, 'INVALID_SEEN')).to.eventually.be.rejected;
});
it('adds notifications without data for all given users via static method', async() => {
@@ -109,41 +112,45 @@ describe('User Model', () => {
let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
- expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
+ expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({});
+ expect(userToJSON.notifications[0].seen).to.eql(false);
user = await User.findOne({_id: otherUser._id}).exec();
userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
- expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
+ expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({});
+ expect(userToJSON.notifications[0].seen).to.eql(false);
});
- it('adds notifications with data for all given users via static method', async() => {
+ it('adds notifications with data and seen status for all given users via static method', async() => {
let user = new User();
let otherUser = new User();
await Bluebird.all([user.save(), otherUser.save()]);
- await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1});
+ await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1}, true);
user = await User.findOne({_id: user._id}).exec();
let userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
- expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
+ expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({field: 1});
+ expect(userToJSON.notifications[0].seen).to.eql(true);
user = await User.findOne({_id: otherUser._id}).exec();
userToJSON = user.toJSON();
expect(user.notifications.length).to.equal(1);
- expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type']);
+ expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
expect(userToJSON.notifications[0].type).to.equal('CRON');
expect(userToJSON.notifications[0].data).to.eql({field: 1});
+ expect(userToJSON.notifications[0].seen).to.eql(true);
});
});
});
@@ -322,6 +329,65 @@ describe('User Model', () => {
user = await user.save();
expect(user.achievements.beastMaster).to.not.equal(true);
});
+
+ context('manage unallocated stats points notifications', () => {
+ it('doesn\'t add a notification if there are no points to allocate', async () => {
+ let user = new User();
+ user = await user.save(); // necessary for user.isSelected to work correctly
+ const oldNotificationsCount = user.notifications.length;
+
+ user.stats.points = 0;
+ user = await user.save();
+
+ expect(user.notifications.length).to.equal(oldNotificationsCount);
+ });
+
+ it('removes a notification if there are no more points to allocate', async () => {
+ let user = new User();
+ user.stats.points = 9;
+ user = await user.save(); // necessary for user.isSelected to work correctly
+
+ expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
+ const oldNotificationsCount = user.notifications.length;
+
+ user.stats.points = 0;
+ user = await user.save();
+
+ expect(user.notifications.length).to.equal(oldNotificationsCount - 1);
+ });
+
+ it('adds a notification if there are points to allocate', async () => {
+ let user = new User();
+ user = await user.save(); // necessary for user.isSelected to work correctly
+ const oldNotificationsCount = user.notifications.length;
+
+ user.stats.points = 9;
+ user = await user.save();
+
+ expect(user.notifications.length).to.equal(oldNotificationsCount + 1);
+ expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
+ expect(user.notifications[0].data.points).to.equal(9);
+ });
+
+ it('adds a notification if the points to allocate have changed', async () => {
+ let user = new User();
+ user.stats.points = 9;
+ user = await user.save(); // necessary for user.isSelected to work correctly
+
+ const oldNotificationsCount = user.notifications.length;
+ const oldNotificationsUUID = user.notifications[0].id;
+ expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
+ expect(user.notifications[0].data.points).to.equal(9);
+
+ user.stats.points = 11;
+ user = await user.save();
+
+ expect(user.notifications.length).to.equal(oldNotificationsCount);
+ expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
+ expect(user.notifications[0].data.points).to.equal(11);
+ expect(user.notifications[0].id).to.not.equal(oldNotificationsUUID);
+ });
+ });
});
context('days missed', () => {
diff --git a/test/common/ops/openMysteryItem.js b/test/common/ops/openMysteryItem.js
index 3766503275..f98a3048b5 100644
--- a/test/common/ops/openMysteryItem.js
+++ b/test/common/ops/openMysteryItem.js
@@ -29,11 +29,14 @@ describe('shared.ops.openMysteryItem', () => {
let mysteryItemKey = 'eyewear_special_summerRogue';
user.purchased.plan.mysteryItems = [mysteryItemKey];
+ user.notifications.push({type: 'NEW_MYSTERY_ITEMS', data: {items: [mysteryItemKey]}});
+ expect(user.notifications.length).to.equal(1);
let [data, message] = openMysteryItem(user);
expect(user.items.gear.owned[mysteryItemKey]).to.be.true;
expect(message).to.equal(i18n.t('mysteryItemOpened'));
expect(data).to.eql(content.gear.flat[mysteryItemKey]);
+ expect(user.notifications.length).to.equal(0);
});
});
diff --git a/test/common/ops/readCard.js b/test/common/ops/readCard.js
index 36ef7aea14..d6d249e1f4 100644
--- a/test/common/ops/readCard.js
+++ b/test/common/ops/readCard.js
@@ -39,10 +39,17 @@ describe('shared.ops.readCard', () => {
});
it('reads a card', () => {
+ user.notifications.push({
+ type: 'CARD_RECEIVED',
+ data: {card: cardType},
+ });
+ const initialNotificationNuber = user.notifications.length;
+
let [, message] = readCard(user, {params: {cardType: 'greeting'}});
expect(message).to.equal(i18n.t('readCard', {cardType}));
expect(user.items.special[`${cardType}Received`]).to.be.empty;
expect(user.flags.cardReceived).to.be.false;
+ expect(user.notifications.length).to.equal(initialNotificationNuber - 1);
});
});
diff --git a/website/client/assets/scss/button.scss b/website/client/assets/scss/button.scss
index a078df469a..843bc24e62 100644
--- a/website/client/assets/scss/button.scss
+++ b/website/client/assets/scss/button.scss
@@ -137,3 +137,9 @@
border: 0;
box-shadow: none;
}
+
+.btn-small {
+ font-size: 12px;
+ line-height: 1.33;
+ padding: 4px 8px;
+}
diff --git a/website/client/assets/scss/dropdown.scss b/website/client/assets/scss/dropdown.scss
index aed164eaf6..efe06ebf7d 100644
--- a/website/client/assets/scss/dropdown.scss
+++ b/website/client/assets/scss/dropdown.scss
@@ -45,6 +45,15 @@
background-color: rgba(#d5c8ff, 0.32);
color: $purple-200;
}
+
+ &.dropdown-inactive {
+ cursor: default;
+
+ &:active, &:hover, &.active {
+ background-color: inherit;
+ color: inherit;
+ }
+ }
}
.dropdown + .dropdown {
diff --git a/website/client/assets/scss/icon.scss b/website/client/assets/scss/icon.scss
index 0295f59daa..78a9b8b8a2 100644
--- a/website/client/assets/scss/icon.scss
+++ b/website/client/assets/scss/icon.scss
@@ -23,6 +23,11 @@
height: 16px;
}
+.icon-12 {
+ width: 12px;
+ height: 12px;
+}
+
.icon-10 {
width: 10px;
height: 10px;
diff --git a/website/client/assets/scss/pin.scss b/website/client/assets/scss/pin.scss
index 556f8f6a13..0874cd8cc6 100644
--- a/website/client/assets/scss/pin.scss
+++ b/website/client/assets/scss/pin.scss
@@ -1,31 +1,26 @@
.badge-svg {
- left: calc((100% - 18px) / 2);
- cursor: pointer;
- color: $gray-400;
- background: $white;
- padding: 4.5px 6px;
+ left: calc((100% - 18px) / 2);
+ cursor: pointer;
+ color: $gray-400;
+ background: $white;
+ padding: 4.5px 6px;
- &.item-selected-badge {
- background: $purple-300;
- color: $white;
- }
+ &.item-selected-badge {
+ background: $purple-300;
+ color: $white;
}
+}
- span.badge.badge-pill.badge-item.badge-svg:not(.item-selected-badge) {
- color: #a5a1ac;
- }
+span.badge.badge-pill.badge-item.badge-svg:not(.item-selected-badge) {
+ color: #a5a1ac;
+}
+span.badge.badge-pill.badge-item.badge-svg.hide {
+ display: none;
+}
+
+.item:hover {
span.badge.badge-pill.badge-item.badge-svg.hide {
- display: none;
+ display: block;
}
-
- .item:hover {
- span.badge.badge-pill.badge-item.badge-svg.hide {
- display: block;
- }
- }
-
- .icon-12 {
- width: 12px;
- height: 12px;
- }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/website/client/assets/scss/static.scss b/website/client/assets/scss/static.scss
index ef542c8b7d..258392852a 100644
--- a/website/client/assets/scss/static.scss
+++ b/website/client/assets/scss/static.scss
@@ -1,26 +1,32 @@
@import '~client/assets/scss/colors.scss';
-.container-fluid {
+.container-fluid.static-view {
margin: 5em 2em 0 2em;
}
-h1, h2 {
- margin-top: 0.5em;
- color: $purple-200;
-}
+.static-view {
+ h1, h2 {
+ margin-top: 0.5em;
+ color: $purple-200;
+ }
-h3, h4 {
- color: $purple-200;
-}
+ h3, h4 {
+ color: $purple-200;
+ }
-li, p {
- font-size: 16px;
-}
+ li, p {
+ font-size: 16px;
+ }
-.media img {
- margin: 1em;
-}
+ .media img {
+ margin: 1em;
+ }
-.strong {
- font-weight: bold;
-}
+ .strong {
+ font-weight: bold;
+ }
+
+ .center-block {
+ margin: 0 auto 1em auto;
+ }
+}
\ No newline at end of file
diff --git a/website/client/assets/scss/typography.scss b/website/client/assets/scss/typography.scss
index 09b09da720..4076d98594 100644
--- a/website/client/assets/scss/typography.scss
+++ b/website/client/assets/scss/typography.scss
@@ -15,8 +15,27 @@ body {
color: $gray-200;
}
-a {
+a, a:not([href]):not([tabindex]) {
cursor: pointer;
+
+ &.standard-link {
+ color: $blue-10;
+
+ &:hover, &:active, &:focus {
+ text-decoration: underline;
+ }
+
+ &[disabled="disabled"] {
+ color: $gray-300;
+ text-decoration: none;
+ cursor: default;
+ }
+ }
+
+ &.small-link {
+ font-size: 12px;
+ line-height: 1.33;
+ }
}
// Headers
diff --git a/website/client/assets/svg/sparkles.svg b/website/client/assets/svg/sparkles.svg
new file mode 100644
index 0000000000..ba7c8a81ef
--- /dev/null
+++ b/website/client/assets/svg/sparkles.svg
@@ -0,0 +1,8 @@
+
diff --git a/website/client/assets/svg/success.svg b/website/client/assets/svg/success.svg
new file mode 100644
index 0000000000..1a0049ca81
--- /dev/null
+++ b/website/client/assets/svg/success.svg
@@ -0,0 +1,21 @@
+
diff --git a/website/client/components/achievements/newStuff.vue b/website/client/components/achievements/newStuff.vue
index 7c2d263f95..fcd8c1df76 100644
--- a/website/client/components/achievements/newStuff.vue
+++ b/website/client/components/achievements/newStuff.vue
@@ -1,58 +1,58 @@
b-modal#new-stuff(
- v-if='user.flags.newStuff',
size='lg',
:hide-header='true',
:hide-footer='true',
)
.modal-body
- new-stuff
+ .static-view(v-html='html')
.modal-footer
a.btn.btn-info(href='http://habitica.wikia.com/wiki/Whats_New', target='_blank') {{ this.$t('newsArchive') }}
- button.btn.btn-secondary(@click='close()') {{ this.$t('cool') }}
+ button.btn.btn-secondary(@click='tellMeLater()') {{ this.$t('tellMeLater') }}
button.btn.btn-warning(@click='dismissAlert();') {{ this.$t('dismissAlert') }}
-
- .modal-body {
- padding-top: 2em;
- }
+
diff --git a/website/client/components/header/menu.vue b/website/client/components/header/menu.vue
index cbf692741d..3d22506fb8 100644
--- a/website/client/components/header/menu.vue
+++ b/website/client/components/header/menu.vue
@@ -57,16 +57,16 @@ div
a.dropdown-item(href="http://habitica.wikia.com/wiki/Habitica_Wiki", target='_blank') {{ $t('wiki') }}
.user-menu.d-flex.align-items-center
.item-with-icon(v-if="userHourglasses > 0")
- .svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')")
+ .top-menu-icon.svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')")
span {{ userHourglasses }}
.item-with-icon
- .svg-icon.gem(v-html="icons.gem", @click='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
+ .top-menu-icon.svg-icon.gem(v-html="icons.gem", @click='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
span {{userGems | roundBigNumber}}
.item-with-icon.gold
- .svg-icon(v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
+ .top-menu-icon.svg-icon(v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
span {{Math.floor(user.stats.gp * 100) / 100}}
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')")
- .svg-icon(v-html="icons.sync")
+ .top-menu-icon.svg-icon(v-html="icons.sync")
notification-menu.item-with-icon
user-dropdown.item-with-icon
@@ -236,11 +236,11 @@ div
margin-right: 24px;
}
- &:hover /deep/ .svg-icon {
+ &:hover /deep/ .top-menu-icon.svg-icon {
color: $white;
}
- & /deep/ .svg-icon {
+ & /deep/ .top-menu-icon.svg-icon {
color: $header-color;
vertical-align: bottom;
display: inline-block;
diff --git a/website/client/components/header/messageCount.vue b/website/client/components/header/messageCount.vue
index 7163808e3e..8ad65de4d1 100644
--- a/website/client/components/header/messageCount.vue
+++ b/website/client/components/header/messageCount.vue
@@ -1,5 +1,7 @@
-span.message-count(:class="{'top-count': props.top === true}") {{props.count}}
+span.message-count(
+ :class="{'top-count': props.top === true, 'top-count-gray': props.gray === true}"
+) {{props.count}}
\ No newline at end of file
diff --git a/website/client/components/header/notifications/base.vue b/website/client/components/header/notifications/base.vue
new file mode 100644
index 0000000000..fc6bcbaa68
--- /dev/null
+++ b/website/client/components/header/notifications/base.vue
@@ -0,0 +1,162 @@
+
+.notification.dropdown-item.dropdown-separated.d-flex.justify-content-between(
+ @click="clicked"
+)
+ .notification-icon.d-flex.justify-content-center.align-items-center(
+ v-if="hasIcon",
+ :class="{'is-not-bailey': isNotBailey}",
+ )
+ slot(name="icon")
+ .notification-content
+ slot(name="content")
+ .notification-remove(@click.stop="canRemove ? remove() : null",)
+ .svg-icon(
+ v-if="canRemove",
+ v-html="icons.close",
+ )
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/cardReceived.vue b/website/client/components/header/notifications/cardReceived.vue
new file mode 100644
index 0000000000..0762f6e09a
--- /dev/null
+++ b/website/client/components/header/notifications/cardReceived.vue
@@ -0,0 +1,35 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="true",
+ :notification="notification",
+ :read-after-click="true",
+ @click="action"
+)
+ div(slot="content", v-html="$t('cardReceived', {card: cardString})")
+ div(slot="icon", :class="cardClass")
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/challengeInvitation.vue b/website/client/components/header/notifications/challengeInvitation.vue
new file mode 100644
index 0000000000..97083da552
--- /dev/null
+++ b/website/client/components/header/notifications/challengeInvitation.vue
@@ -0,0 +1,3 @@
+
+div {{ props.notification }}
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/groupTaskApproval.vue b/website/client/components/header/notifications/groupTaskApproval.vue
new file mode 100644
index 0000000000..305d1bd799
--- /dev/null
+++ b/website/client/components/header/notifications/groupTaskApproval.vue
@@ -0,0 +1,72 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="false",
+ :notification="notification",
+ @click="action",
+)
+ div(slot="content")
+ div(v-html="notification.data.message")
+ .notifications-buttons
+ .btn.btn-small.btn-success(@click.stop="approve()") {{ $t('approve') }}
+ .btn.btn-small.btn-warning(@click.stop="needsWork()") {{ $t('needsWork') }}
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/groupTaskApproved.vue b/website/client/components/header/notifications/groupTaskApproved.vue
new file mode 100644
index 0000000000..35982f3ef9
--- /dev/null
+++ b/website/client/components/header/notifications/groupTaskApproved.vue
@@ -0,0 +1,27 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="false",
+ :notification="notification",
+ :read-after-click="true",
+ @click="action",
+)
+ .notification-green(slot="content", v-html="notification.data.message")
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/groupTaskNeedsWork.vue b/website/client/components/header/notifications/groupTaskNeedsWork.vue
new file mode 100644
index 0000000000..068e479f72
--- /dev/null
+++ b/website/client/components/header/notifications/groupTaskNeedsWork.vue
@@ -0,0 +1,27 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="false",
+ :notification="notification",
+ :read-after-click="true",
+ @click="action",
+)
+ .notification-yellow(slot="content", v-html="notification.data.message")
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/guildInvitation.vue b/website/client/components/header/notifications/guildInvitation.vue
new file mode 100644
index 0000000000..6920e36392
--- /dev/null
+++ b/website/client/components/header/notifications/guildInvitation.vue
@@ -0,0 +1,64 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="false",
+ :notification="notification",
+ @click="action",
+)
+ div(slot="content")
+ div(v-html="textString")
+ .notifications-buttons
+ .btn.btn-small.btn-success(@click.stop="accept()") {{ $t('accept') }}
+ .btn.btn-small.btn-danger(@click.stop="reject()") {{ $t('reject') }}
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/newChatMessage.vue b/website/client/components/header/notifications/newChatMessage.vue
new file mode 100644
index 0000000000..42c86248a4
--- /dev/null
+++ b/website/client/components/header/notifications/newChatMessage.vue
@@ -0,0 +1,46 @@
+
+// Read automatically from the group page mounted hook
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="false",
+ :notification="notification",
+ :read-after-click="false",
+ @click="action"
+)
+ div(slot="content", v-html="string")
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/newInboxMessage.vue b/website/client/components/header/notifications/newInboxMessage.vue
new file mode 100644
index 0000000000..05f76b2f15
--- /dev/null
+++ b/website/client/components/header/notifications/newInboxMessage.vue
@@ -0,0 +1,28 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="false",
+ :notification="notification",
+ :read-after-click="true",
+ @click="action"
+)
+ div(slot="content")
+ span(v-html="$t('userSentMessage', {user: notification.data.sender.name})")
+ .notification-small.notification-ellipses {{ notification.data.excerpt }}
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/newMysteryItems.vue b/website/client/components/header/notifications/newMysteryItems.vue
new file mode 100644
index 0000000000..763b848b11
--- /dev/null
+++ b/website/client/components/header/notifications/newMysteryItems.vue
@@ -0,0 +1,33 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="true",
+ :notification="notification",
+ :read-after-click="true",
+ @click="action"
+)
+ div(slot="content", v-html="$t('newSubscriberItem')")
+ div(slot="icon", :class="mysteryClass")
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/newStuff.vue b/website/client/components/header/notifications/newStuff.vue
new file mode 100644
index 0000000000..a479748413
--- /dev/null
+++ b/website/client/components/header/notifications/newStuff.vue
@@ -0,0 +1,29 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="true",
+ :notification="notification",
+ :read-after-click="true",
+ @click="action"
+)
+ div(slot="content")
+ .notification-bold-purple {{ $t('newBaileyUpdate') }}
+ div {{ notification.data.title }}
+ .npc_bailey(slot="icon")
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/partyInvitation.vue b/website/client/components/header/notifications/partyInvitation.vue
new file mode 100644
index 0000000000..a93d277970
--- /dev/null
+++ b/website/client/components/header/notifications/partyInvitation.vue
@@ -0,0 +1,42 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="false",
+ :notification="notification",
+)
+ div(slot="content")
+ div(v-html="$t('invitedToParty', {party: notification.data.name})")
+ .notifications-buttons
+ .btn.btn-small.btn-success(@click.stop="accept()") {{ $t('accept') }}
+ .btn.btn-small.btn-danger(@click.stop="reject()") {{ $t('reject') }}
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/questInvitation.vue b/website/client/components/header/notifications/questInvitation.vue
new file mode 100644
index 0000000000..ce8e6b9106
--- /dev/null
+++ b/website/client/components/header/notifications/questInvitation.vue
@@ -0,0 +1,63 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="false",
+ :notification="notification",
+ @click="action",
+)
+ div(slot="content")
+ .message(v-html="$t('invitedToQuest', {quest: questName})")
+ quest-info(:quest="questData", :small-version="true")
+ .notifications-buttons
+ .btn.btn-small.btn-success(@click.stop="questAccept()") {{ $t('accept') }}
+ .btn.btn-small.btn-danger(@click.stop="questReject()") {{ $t('reject') }}
+
+
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notifications/unallocatedStatsPoints.vue b/website/client/components/header/notifications/unallocatedStatsPoints.vue
new file mode 100644
index 0000000000..8841750fbb
--- /dev/null
+++ b/website/client/components/header/notifications/unallocatedStatsPoints.vue
@@ -0,0 +1,45 @@
+
+base-notification(
+ :can-remove="canRemove",
+ :has-icon="true",
+ :notification="notification",
+ :read-after-click="true",
+ @click="action"
+)
+ div(slot="content", v-html="$t('unallocatedStatsPoints', {points: notification.data.points})")
+ .svg-icon(slot="icon", v-html="icons.sparkles")
+
+
+
+
+
\ No newline at end of file
diff --git a/website/client/components/header/notificationsDropdown.vue b/website/client/components/header/notificationsDropdown.vue
index 2b29937a91..a2849fa0bb 100644
--- a/website/client/components/header/notificationsDropdown.vue
+++ b/website/client/components/header/notificationsDropdown.vue
@@ -1,289 +1,254 @@
-menu-dropdown.item-notifications(:right="true")
+menu-dropdown.item-notifications(:right="true", @toggled="handleOpenStatusChange", :openStatus="openStatus")
div(slot="dropdown-toggle")
div(v-b-tooltip.hover.bottom="$t('notifications')")
- message-count(v-if='notificationsCount > 0', :count="notificationsCount", :top="true")
- .svg-icon.notifications(v-html="icons.notifications")
+ message-count(
+ v-if='notificationsCount > 0',
+ :count="notificationsCount",
+ :top="true",
+ :gray="!hasUnseenNotifications",
+ )
+ .top-menu-icon.svg-icon.notifications(v-html="icons.notifications")
div(slot="dropdown-content")
- h4.dropdown-item.dropdown-separated(v-if='!hasNoNotifications()') {{ $t('notifications') }}
- h4.dropdown-item.toolbar-notifs-no-messages(v-if='hasNoNotifications()') {{ $t('noNotifications') }}
- a.dropdown-item(v-if='user.party.quest && user.party.quest.RSVPNeeded')
- div {{ $t('invitedTo', {name: quests.quests[user.party.quest.key].text()}) }}
- div
- button.btn.btn-primary(@click.stop='questAccept(user.party._id)') Accept
- button.btn.btn-primary(@click.stop='questReject(user.party._id)') Reject
- a.dropdown-item(v-if='user.purchased.plan.mysteryItems.length', @click='go("/inventory/items")')
- span.glyphicon.glyphicon-gift
- span {{ $t('newSubscriberItem') }}
- a.dropdown-item(v-for='(party, index) in user.invitations.parties', :key='party.id')
- div
- span.glyphicon.glyphicon-user
- span {{ $t('invitedTo', {name: party.name}) }}
- div
- button.btn.btn-primary(@click.stop='accept(party, index, "party")') Accept
- button.btn.btn-primary(@click.stop='reject(party, index, "party")') Reject
- a.dropdown-item(v-if='user.flags.cardReceived', @click='go("/inventory/items")')
- span.glyphicon.glyphicon-envelope
- span {{ $t('cardReceived') }}
- a.dropdown-item(@click.stop='clearCards()')
- a.dropdown-item(v-for='(guild, index) in user.invitations.guilds', :key='guild.id')
- div
- span.glyphicon.glyphicon-user
- span {{ $t('invitedTo', {name: guild.name}) }}
- div
- button.btn.btn-primary(@click.stop='accept(guild, index, "guild")') Accept
- button.btn.btn-primary(@click.stop='reject(guild, index, "guild")') Reject
- a.dropdown-item(v-if='user.flags.classSelected && !user.preferences.disableClasses && user.stats.points',
- @click='showProfile()')
- span.glyphicon.glyphicon-plus-sign
- span {{ $t('haveUnallocated', {points: user.stats.points}) }}
- a.dropdown-item(v-for='message in userNewMessages', :key='message.key')
- span(@click='navigateToGroup(message.key)')
- span.glyphicon.glyphicon-comment
- span {{message.name}}
- span.clear-button(@click.stop='clearMessages(message.key)') Clear
- a.dropdown-item(v-for='notification in groupNotifications', :key='notification.id')
- span(:class="groupApprovalNotificationIcon(notification)")
- span {{notification.data.message}}
- span.clear-button(@click.stop='viewGroupApprovalNotification(notification)') Clear
+ .dropdown-item.dropdown-separated.d-flex.justify-content-between.dropdown-inactive.align-items-center(
+ @click.stop=""
+ )
+ h4.dropdown-title(v-once) {{ $t('notifications') }}
+ a.small-link.standard-link(@click="dismissAll", :disabled="notificationsCount === 0") {{ $t('dismissAll') }}
+ component(
+ :is="notification.type",
+ :key="notification.id",
+ v-for="notification in notifications",
+ :notification="notification",
+ :can-remove="!isActionable(notification)",
+ )
+ .dropdown-item.dropdown-separated.d-flex.justify-content-center.dropdown-inactive.no-notifications.flex-column(
+ v-if="notificationsCount === 0"
+ )
+ .svg-icon(v-html="icons.success")
+ h2 You're all caught up!
+ p The notification fairies give you a raucous round of applause! Well done!
+
diff --git a/website/client/components/static/home.vue b/website/client/components/static/home.vue
index 8f20858108..2273ed6884 100644
--- a/website/client/components/static/home.vue
+++ b/website/client/components/static/home.vue
@@ -1,5 +1,5 @@
- #front
+ #front.static-view
noscript.banner {{ $t('jsDisabledHeadingFull') }}
br
a(href='http://www.enable-javascript.com/', target='_blank') {{ $t('jsDisabledLink') }}
@@ -118,8 +118,12 @@
.seamless_stars_varied_opacity_repeat
+
+
diff --git a/website/client/components/static/overview.vue b/website/client/components/static/overview.vue
index c6698d7865..cfd14c700b 100644
--- a/website/client/components/static/overview.vue
+++ b/website/client/components/static/overview.vue
@@ -1,5 +1,5 @@
-.container-fluid
+.container-fluid.static-view
.row
.col-md-6.offset-3
h1 {{ $t('overview') }}
@@ -11,9 +11,11 @@
p(v-markdown="$t('overviewQuestions')")
-
+