mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-13 12:47:28 +01:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
484c3cbac8 | ||
|
|
c199beaf8c | ||
|
|
553aa01c25 | ||
|
|
8d1b10e458 | ||
|
|
0eaee9b1e4 | ||
|
|
41bbc475ab | ||
|
|
d6e03c765e | ||
|
|
dd6503d5ef | ||
|
|
36e5f39d7c | ||
|
|
d48e4a664f | ||
|
|
661b30e807 | ||
|
|
026e819271 | ||
|
|
1fab19acf4 | ||
|
|
5743fb86b0 | ||
|
|
5443bf2459 | ||
|
|
c0d5566417 | ||
|
|
ded71b46c5 | ||
|
|
9693ad321c | ||
|
|
dd3679f329 | ||
|
|
f3029953dc | ||
|
|
01881b2fd8 | ||
|
|
11a22d0f5d | ||
|
|
5f9bf07045 | ||
|
|
719c03e2f5 | ||
|
|
379afa9554 | ||
|
|
dbc23e89b8 | ||
|
|
0c6e254742 | ||
|
|
8327e69bdd | ||
|
|
2d953f4f59 | ||
|
|
7118d63949 | ||
|
|
20af8d038e | ||
|
|
3d9dfbb5e1 | ||
|
|
ae0b966f45 | ||
|
|
cef8a34c06 | ||
|
|
6432823eec | ||
|
|
563b780d85 | ||
|
|
aa9b1b2cac | ||
|
|
401e541b86 | ||
|
|
c13bed3bad | ||
|
|
b3c4817fb4 | ||
|
|
7c9c45ac5f | ||
|
|
95142e3684 | ||
|
|
dc1cce6ddb | ||
|
|
43cf77f33c | ||
|
|
93780d7056 | ||
|
|
2ad17d408e | ||
|
|
b0f7567367 | ||
|
|
3f2b1d3f79 | ||
|
|
29eb8ca10b | ||
|
|
8c71ca12b8 | ||
|
|
72a753626f | ||
|
|
35ebb12bf2 | ||
|
|
1ff418f62d | ||
|
|
e1aa437ea5 | ||
|
|
2a4239bf3c | ||
|
|
399563435b | ||
|
|
59f7e25c85 | ||
|
|
ad845dff43 | ||
|
|
fd1eb2d900 | ||
|
|
26cb6df9d9 | ||
|
|
b0aafb079a | ||
|
|
58f0837c50 | ||
|
|
a6378b3d43 | ||
|
|
ddbf95da92 | ||
|
|
4d31e0286b | ||
|
|
7a74825121 | ||
|
|
be0e8779d5 | ||
|
|
fffbe17bcc | ||
|
|
ca4ee8b513 | ||
|
|
30f1820a49 | ||
|
|
3bb6c391af | ||
|
|
a0383c785a | ||
|
|
99790c05f4 | ||
|
|
fc5fec9bfe | ||
|
|
9db5d4116d | ||
|
|
6676e94ef6 | ||
|
|
723adceb25 | ||
|
|
440d06da4a | ||
|
|
0ea84668a8 | ||
|
|
5893d8b9bb | ||
|
|
2c799b9c07 | ||
|
|
1550d9b4ee | ||
|
|
ade812b86d | ||
|
|
62e6fbef61 | ||
|
|
67a0f8b65a | ||
|
|
aa432022d3 | ||
|
|
86fb3c1fd1 | ||
|
|
ff2b4add8b | ||
|
|
4ba73dfbec | ||
|
|
e675ea9bd1 | ||
|
|
9c27d86ced | ||
|
|
58ee81adfc | ||
|
|
32c9904a6e | ||
|
|
b86e0a1549 | ||
|
|
154ac9bb38 | ||
|
|
a97060445a | ||
|
|
26b59de1de | ||
|
|
21c8b00ef6 |
@@ -7,5 +7,14 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
'prefer-regex-literals': 'warn',
|
'prefer-regex-literals': 'warn',
|
||||||
'import/no-extraneous-dependencies': 'off',
|
'import/no-extraneous-dependencies': 'off',
|
||||||
|
'require-await': 'error',
|
||||||
},
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['migrations/**', 'gulp/**'], // Or *.test.js
|
||||||
|
rules: {
|
||||||
|
'require-await': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -1,6 +1,13 @@
|
|||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'phillip/**'
|
||||||
|
- 'sabrecat/**'
|
||||||
|
- 'kalista/**'
|
||||||
|
- 'natalie/**'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -47,5 +47,5 @@ webpack.webstorm.config
|
|||||||
|
|
||||||
# mongodb replica set for local dev
|
# mongodb replica set for local dev
|
||||||
mongodb-*.tgz
|
mongodb-*.tgz
|
||||||
/mongodb-data
|
/mongodb-data*
|
||||||
/.nyc_output
|
/.nyc_output
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
This webpage includes the documentation for version 3 of the [Habitica](https://habitica.com) API.
|
This webpage includes the documentation for version 3 of the [Habitica](https://habitica.com) API.
|
||||||
|
|
||||||
If you're developing a 3rd party tool that uses the Habitica API you should read the [Guidance for Comrades](https://habitica.fandom.com/wiki/Guidance_for_Comrades) and in particular the section called [Rules for Third-Party Tools](https://habitica.fandom.com/wiki/Guidance_for_Comrades#Rules_for_Third-Party_Tools) which includes suggestions on how to best use the API and the rules to follow when interacting with it.
|
If you're developing a 3rd party tool that uses the Habitica API, read the [API Usage Guidelines](https://github.com/HabitRPG/habitica/wiki/API-Usage-Guidelines), which describe how to be a responsible user of our server resources!
|
||||||
|
|||||||
@@ -93,5 +93,6 @@
|
|||||||
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
|
"TRUSTED_DOMAINS": "localhost,https://habitica.com",
|
||||||
"TIME_TRAVEL_ENABLED": "false",
|
"TIME_TRAVEL_ENABLED": "false",
|
||||||
"DEBUG_ENABLED": "false",
|
"DEBUG_ENABLED": "false",
|
||||||
"CONTENT_SWITCHOVER_TIME_OFFSET": 8
|
"CONTENT_SWITCHOVER_TIME_OFFSET": 8,
|
||||||
|
"SLOW_REQUEST_THRESHOLD": 1000
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,15 @@ function filterFile (file) {
|
|||||||
if (file.relative.indexOf('icon_background') === 0) {
|
if (file.relative.indexOf('icon_background') === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (file.relative.indexOf('notif_') === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.relative.indexOf('quest_') === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.relative.indexOf('inventory_quest_') === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Submodule habitica-images updated: dedbcf0f24...142936a93a
@@ -26,7 +26,7 @@ async function updateUser (user) {
|
|||||||
[{ name: 'BASE_URL', content: BASE_URL }], // Add variables from template
|
[{ name: 'BASE_URL', content: BASE_URL }], // Add variables from template
|
||||||
);
|
);
|
||||||
|
|
||||||
return User.update({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
|
return User.updateOne({ _id: user._id }, { $set: { migration: MIGRATION_NAME } }).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ async function updateUser (user) {
|
|||||||
|
|
||||||
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
return User.update({ _id: user._id }, { $set: set }).exec();
|
return User.updateOne({ _id: user._id }, { $set: set }).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
const query = {
|
const query = {
|
||||||
migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.local.lowerCaseUsername': 'olson1',
|
'auth.local.username': 'ExampleHabitican',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ async function updateUser (user) {
|
|||||||
export default async function processUsers () {
|
export default async function processUsers () {
|
||||||
const query = {
|
const query = {
|
||||||
migration: { $ne: MIGRATION_NAME },
|
migration: { $ne: MIGRATION_NAME },
|
||||||
'auth.local.username': 'SabreTest',
|
'auth.local.username': 'ExampleHabitican',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
|
|||||||
125
migrations/users/nye.js
Normal file
125
migrations/users/nye.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import { model as User } from '../../website/server/models/user';
|
||||||
|
|
||||||
|
const MIGRATION_NAME = '20231228_nye';
|
||||||
|
const progressCount = 1000;
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
async function updateUser (user) {
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
const updateOp = {
|
||||||
|
$set: { migration: MIGRATION_NAME },
|
||||||
|
$push: { },
|
||||||
|
};
|
||||||
|
const data = {
|
||||||
|
title: 'Happy New Year!',
|
||||||
|
destination: '/inventory/equipment',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof user.items.gear.owned.head_special_nye2023 !== 'undefined') {
|
||||||
|
updateOp.$inc = {
|
||||||
|
'items.food.Candy_Skeleton': 1,
|
||||||
|
'items.food.Candy_Base': 1,
|
||||||
|
'items.food.Candy_CottonCandyBlue': 1,
|
||||||
|
'items.food.Candy_CottonCandyPink': 1,
|
||||||
|
'items.food.Candy_Shade': 1,
|
||||||
|
'items.food.Candy_White': 1,
|
||||||
|
'items.food.Candy_Golden': 1,
|
||||||
|
'items.food.Candy_Zombie': 1,
|
||||||
|
'items.food.Candy_Desert': 1,
|
||||||
|
'items.food.Candy_Red': 1,
|
||||||
|
};
|
||||||
|
data.icon = 'notif_candy_nye';
|
||||||
|
data.text = 'You’ve received an assortment of candy to celebrate with your Pets!';
|
||||||
|
data.destination = '/inventory/stable';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2022 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2023'] = true;
|
||||||
|
data.icon = 'notif_2023hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Ludicrous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2021 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2022'] = true;
|
||||||
|
data.icon = 'notif_2022hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Fabulous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2020 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2021'] = true;
|
||||||
|
data.icon = 'notif_2021hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Preposterous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2019 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2020'] = true;
|
||||||
|
data.icon = 'notif_2020hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Extravagant Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2018 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2019'] = true;
|
||||||
|
data.icon = 'notif_2019hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Outrageous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2017 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2018'] = true;
|
||||||
|
data.icon = 'notif_2018hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Outlandish Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2016 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2017'] = true;
|
||||||
|
data.icon = 'notif_2017hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Fanciful Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2015 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2016'] = true;
|
||||||
|
data.icon = 'notif_2016hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Whimsical Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye2014 !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2015'] = true;
|
||||||
|
data.icon = 'notif_2015hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Ridiculous Party Hat!';
|
||||||
|
} else if (typeof user.items.gear.owned.head_special_nye !== 'undefined') {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye2014'] = true;
|
||||||
|
data.icon = 'notif_2014hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Silly Party Hat!';
|
||||||
|
} else {
|
||||||
|
updateOp.$set['items.gear.owned.head_special_nye'] = true;
|
||||||
|
data.icon = 'notif_2013hat_nye';
|
||||||
|
data.text = 'Take on your resolutions with style in this Absurd Party Hat!';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOp.$push.notifications = {
|
||||||
|
type: 'ITEM_RECEIVED',
|
||||||
|
data,
|
||||||
|
seen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
|
||||||
|
|
||||||
|
return User.updateOne({ _id: user._id }, updateOp).exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function processUsers () {
|
||||||
|
const query = {
|
||||||
|
'auth.timestamps.loggedin': { $gt: new Date('2023-12-01') },
|
||||||
|
migration: { $ne: MIGRATION_NAME },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
_id: 1,
|
||||||
|
items: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) { // eslint-disable-line no-constant-condition
|
||||||
|
const users = await User // eslint-disable-line no-await-in-loop
|
||||||
|
.find(query)
|
||||||
|
.limit(250)
|
||||||
|
.sort({ _id: 1 })
|
||||||
|
.select(fields)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.warn('All appropriate users found and modified.');
|
||||||
|
console.warn(`\n${count} users processed\n`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
query._id = {
|
||||||
|
$gt: users[users.length - 1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
|
||||||
|
}
|
||||||
|
}
|
||||||
461
package-lock.json
generated
461
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"version": "5.32.0",
|
"version": "5.36.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"version": "5.32.0",
|
"version": "5.36.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.10",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"apple-auth": "^1.0.9",
|
"apple-auth": "^1.0.9",
|
||||||
"babel-preset-env": "^1.7.0",
|
"babel-preset-env": "^1.7.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.3",
|
||||||
"bootstrap": "^4.6.2",
|
"bootstrap": "^4.6.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-session": "^2.0.0",
|
"cookie-session": "^2.0.0",
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"eslint": "^8.55.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-habitrpg": "^6.2.3",
|
"eslint-config-habitrpg": "^6.2.3",
|
||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
"express": "^4.19.2",
|
"express": "^4.21.1",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-validator": "^5.2.0",
|
"express-validator": "^5.2.0",
|
||||||
"firebase-admin": "^12.1.1",
|
"firebase-admin": "^12.1.1",
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
"method-override": "^3.0.0",
|
"method-override": "^3.0.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-recur": "^1.0.7",
|
"moment-recur": "^1.0.7",
|
||||||
"mongoose": "^7.6.3",
|
"mongoose": "^8.9.5",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"node-gcm": "^1.0.5",
|
"node-gcm": "^1.0.5",
|
||||||
@@ -3044,10 +3044,10 @@
|
|||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
},
|
},
|
||||||
"node_modules/@mongodb-js/saslprep": {
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
|
||||||
"integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==",
|
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
|
||||||
"optional": true,
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sparse-bitfield": "^3.0.3"
|
"sparse-bitfield": "^3.0.3"
|
||||||
}
|
}
|
||||||
@@ -3677,14 +3677,15 @@
|
|||||||
"node_modules/@types/webidl-conversions": {
|
"node_modules/@types/webidl-conversions": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||||
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
|
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/whatwg-url": {
|
"node_modules/@types/whatwg-url": {
|
||||||
"version": "8.2.2",
|
"version": "11.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||||
"integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
|
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
|
||||||
"@types/webidl-conversions": "*"
|
"@types/webidl-conversions": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6244,9 +6245,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||||
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "3.1.2",
|
||||||
"content-type": "~1.0.5",
|
"content-type": "~1.0.5",
|
||||||
@@ -6256,7 +6257,7 @@
|
|||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"iconv-lite": "0.4.24",
|
"iconv-lite": "0.4.24",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"qs": "6.11.0",
|
"qs": "6.13.0",
|
||||||
"raw-body": "2.5.2",
|
"raw-body": "2.5.2",
|
||||||
"type-is": "~1.6.18",
|
"type-is": "~1.6.18",
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
@@ -6291,11 +6292,11 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
"node_modules/body-parser/node_modules/qs": {
|
"node_modules/body-parser/node_modules/qs": {
|
||||||
"version": "6.11.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.0.4"
|
"side-channel": "^1.0.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
@@ -6401,10 +6402,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bson": {
|
"node_modules/bson": {
|
||||||
"version": "6.3.0",
|
"version": "6.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/bson/-/bson-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.2.tgz",
|
||||||
"integrity": "sha512-balJfqwwTBddxfnidJZagCBPP/f48zj9Sdp3OJswREOgsJzHiQSaOIAtApSgDQFYgHqAvFkp53AFSqjMDZoTFw==",
|
"integrity": "sha512-5afhLTjqDSA3akH56E+/2J6kTDuSIlBxyXPdQslj9hcIgOUE378xdOfZvC/9q3LifJNI6KR/juZ+d0NRNYBwXg==",
|
||||||
"dev": true,
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.20.1"
|
"node": ">=16.20.1"
|
||||||
}
|
}
|
||||||
@@ -6577,14 +6578,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||||
"integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==",
|
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"function-bind": "^1.1.2",
|
"function-bind": "^1.1.2",
|
||||||
"get-intrinsic": "^1.2.3",
|
"get-intrinsic": "^1.2.4",
|
||||||
"set-function-length": "^1.2.0"
|
"set-function-length": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -7369,9 +7371,9 @@
|
|||||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
"version": "0.6.0",
|
"version": "0.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -8376,9 +8378,9 @@
|
|||||||
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
|
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
|
||||||
},
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
"version": "1.0.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -8497,6 +8499,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
|
||||||
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
|
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"get-intrinsic": "^1.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-errors": {
|
"node_modules/es-errors": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
@@ -9980,36 +9993,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.19.2",
|
"version": "4.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
||||||
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.3",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"content-type": "~1.0.4",
|
"content-type": "~1.0.4",
|
||||||
"cookie": "0.6.0",
|
"cookie": "0.7.1",
|
||||||
"cookie-signature": "1.0.6",
|
"cookie-signature": "1.0.6",
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"etag": "~1.8.1",
|
"etag": "~1.8.1",
|
||||||
"finalhandler": "1.2.0",
|
"finalhandler": "1.3.1",
|
||||||
"fresh": "0.5.2",
|
"fresh": "0.5.2",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"merge-descriptors": "1.0.1",
|
"merge-descriptors": "1.0.3",
|
||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.7",
|
"path-to-regexp": "0.1.10",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.11.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
"safe-buffer": "5.2.1",
|
"safe-buffer": "5.2.1",
|
||||||
"send": "0.18.0",
|
"send": "0.19.0",
|
||||||
"serve-static": "1.15.0",
|
"serve-static": "1.16.2",
|
||||||
"setprototypeof": "1.2.0",
|
"setprototypeof": "1.2.0",
|
||||||
"statuses": "2.0.1",
|
"statuses": "2.0.1",
|
||||||
"type-is": "~1.6.18",
|
"type-is": "~1.6.18",
|
||||||
@@ -10062,11 +10075,11 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
"node_modules/express/node_modules/qs": {
|
"node_modules/express/node_modules/qs": {
|
||||||
"version": "6.11.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.0.4"
|
"side-channel": "^1.0.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
@@ -10531,12 +10544,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
@@ -13348,11 +13361,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
|
||||||
"integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA=="
|
"integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA=="
|
||||||
},
|
},
|
||||||
"node_modules/ip": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
|
|
||||||
},
|
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -14267,9 +14275,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/kareem": {
|
"node_modules/kareem": {
|
||||||
"version": "2.5.1",
|
"version": "2.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
|
||||||
"integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==",
|
"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
@@ -14278,7 +14287,7 @@
|
|||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/kerberos/-/kerberos-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/kerberos/-/kerberos-1.1.7.tgz",
|
||||||
"integrity": "sha512-1zXg4rARjsh/VMz2jjZeTfRHbJTVNR6f2DYHbLvtUSOW1satj33Fvc7vOJ0YVWB9+/9ITJWd1QKp4w217SsiFA==",
|
"integrity": "sha512-1zXg4rARjsh/VMz2jjZeTfRHbJTVNR6f2DYHbLvtUSOW1satj33Fvc7vOJ0YVWB9+/9ITJWd1QKp4w217SsiFA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
@@ -14936,8 +14945,7 @@
|
|||||||
"node_modules/memory-pager": {
|
"node_modules/memory-pager": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||||
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/meow": {
|
"node_modules/meow": {
|
||||||
"version": "3.7.0",
|
"version": "3.7.0",
|
||||||
@@ -14961,9 +14969,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/merge-stream": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -15443,43 +15454,47 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongodb-connection-string-url": {
|
"node_modules/mongodb-connection-string-url": {
|
||||||
"version": "2.6.0",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
|
||||||
"integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
|
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/whatwg-url": "^8.2.1",
|
"@types/whatwg-url": "^11.0.2",
|
||||||
"whatwg-url": "^11.0.0"
|
"whatwg-url": "^14.1.0 || ^13.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
|
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
|
||||||
"version": "3.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
|
||||||
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
|
"integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"punycode": "^2.1.1"
|
"punycode": "^2.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
|
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
|
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
|
||||||
"version": "11.0.0",
|
"version": "14.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz",
|
||||||
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
|
"integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tr46": "^3.0.0",
|
"tr46": "^5.0.0",
|
||||||
"webidl-conversions": "^7.0.0"
|
"webidl-conversions": "^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongodb-core": {
|
"node_modules/mongodb-core": {
|
||||||
@@ -15564,55 +15579,64 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose": {
|
"node_modules/mongoose": {
|
||||||
"version": "7.6.8",
|
"version": "8.9.7",
|
||||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.6.8.tgz",
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.7.tgz",
|
||||||
"integrity": "sha512-q9zAySH+UtOK5yonWyNcLfq3PxrY6s4gdta4qNGKNOE2yTVoY9FP4hQtvWYnv4rkdk7T8QmQMC7bbhJjDxIunw==",
|
"integrity": "sha512-mvNXmU0V8qZzMR/qoK2mjT4Ti2ALdtfS0teK+twxhlGkwzOD76V02/zWajTu2MJ7QyEmZe9OWvnJsIY0iAuX3Q==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^5.5.0",
|
"bson": "^6.10.1",
|
||||||
"kareem": "2.5.1",
|
"kareem": "2.6.3",
|
||||||
"mongodb": "5.9.1",
|
"mongodb": "~6.12.0",
|
||||||
"mpath": "0.9.0",
|
"mpath": "0.9.0",
|
||||||
"mquery": "5.0.0",
|
"mquery": "5.0.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"sift": "16.0.1"
|
"sift": "17.1.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.20.1"
|
"node": ">=16.20.1"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/mongoose"
|
"url": "https://opencollective.com/mongoose"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose/node_modules/bson": {
|
"node_modules/mongoose/node_modules/kerberos": {
|
||||||
"version": "5.5.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/kerberos/-/kerberos-2.2.1.tgz",
|
||||||
"integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==",
|
"integrity": "sha512-Vlyv1tjAPb0y2VIJ03dKkUjsneGIBuTkH24uGRx6/DrKpFlVuGPmct3m5aEotljVUlw7PAGWABwR5aNeW7y8Zw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-addon-api": "^6.1.0",
|
||||||
|
"prebuild-install": "^7.1.2"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.20.1"
|
"node": ">=12.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mongoose/node_modules/mongodb": {
|
"node_modules/mongoose/node_modules/mongodb": {
|
||||||
"version": "5.9.1",
|
"version": "6.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz",
|
||||||
"integrity": "sha512-NBGA8AfJxGPeB12F73xXwozt8ZpeIPmCUeWRwl9xejozTXFes/3zaep9zhzs1B/nKKsw4P3I4iPfXl3K7s6g+Q==",
|
"integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bson": "^5.5.0",
|
"@mongodb-js/saslprep": "^1.1.9",
|
||||||
"mongodb-connection-string-url": "^2.6.0",
|
"bson": "^6.10.1",
|
||||||
"socks": "^2.7.1"
|
"mongodb-connection-string-url": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.20.1"
|
"node": ">=16.20.1"
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@mongodb-js/saslprep": "^1.1.0"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@aws-sdk/credential-providers": "^3.188.0",
|
"@aws-sdk/credential-providers": "^3.188.0",
|
||||||
"@mongodb-js/zstd": "^1.0.0",
|
"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
|
||||||
"kerberos": "^1.0.0 || ^2.0.0",
|
"gcp-metadata": "^5.2.0",
|
||||||
"mongodb-client-encryption": ">=2.3.0 <3",
|
"kerberos": "^2.0.1",
|
||||||
"snappy": "^7.2.2"
|
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||||
|
"snappy": "^7.2.2",
|
||||||
|
"socks": "^2.7.1"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@aws-sdk/credential-providers": {
|
"@aws-sdk/credential-providers": {
|
||||||
@@ -15621,6 +15645,9 @@
|
|||||||
"@mongodb-js/zstd": {
|
"@mongodb-js/zstd": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"gcp-metadata": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"kerberos": {
|
"kerberos": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@@ -15629,6 +15656,9 @@
|
|||||||
},
|
},
|
||||||
"snappy": {
|
"snappy": {
|
||||||
"optional": true
|
"optional": true
|
||||||
|
},
|
||||||
|
"socks": {
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -15637,6 +15667,105 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/mongoose/node_modules/napi-build-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/mongoose/node_modules/node-abi": {
|
||||||
|
"version": "3.74.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",
|
||||||
|
"integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.3.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongoose/node_modules/node-addon-api": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/mongoose/node_modules/prebuild-install": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.0",
|
||||||
|
"expand-template": "^2.0.3",
|
||||||
|
"github-from-package": "0.0.0",
|
||||||
|
"minimist": "^1.2.3",
|
||||||
|
"mkdirp-classic": "^0.5.3",
|
||||||
|
"napi-build-utils": "^2.0.0",
|
||||||
|
"node-abi": "^3.3.0",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"rc": "^1.2.7",
|
||||||
|
"simple-get": "^4.0.0",
|
||||||
|
"tar-fs": "^2.0.0",
|
||||||
|
"tunnel-agent": "^0.6.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prebuild-install": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongoose/node_modules/semver": {
|
||||||
|
"version": "7.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||||
|
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongoose/node_modules/simple-get": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"decompress-response": "^6.0.0",
|
||||||
|
"once": "^1.3.1",
|
||||||
|
"simple-concat": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/monk": {
|
"node_modules/monk": {
|
||||||
"version": "7.3.4",
|
"version": "7.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/monk/-/monk-7.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/monk/-/monk-7.3.4.tgz",
|
||||||
@@ -16050,7 +16179,7 @@
|
|||||||
"version": "2.30.1",
|
"version": "2.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz",
|
||||||
"integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==",
|
"integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"semver": "^5.4.1"
|
"semver": "^5.4.1"
|
||||||
}
|
}
|
||||||
@@ -16059,7 +16188,7 @@
|
|||||||
"version": "5.7.2",
|
"version": "5.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
}
|
}
|
||||||
@@ -16249,7 +16378,7 @@
|
|||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
|
||||||
"integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==",
|
"integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/nopt": {
|
"node_modules/nopt": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
@@ -17592,9 +17721,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
||||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
|
||||||
},
|
},
|
||||||
"node_modules/path-type": {
|
"node_modules/path-type": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@@ -17864,7 +17993,7 @@
|
|||||||
"version": "6.1.2",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.2.tgz",
|
||||||
"integrity": "sha512-PzYWIKZeP+967WuKYXlTOhYBgGOvTRSfaKI89XnfJ0ansRAH7hDU45X+K+FZeI1Wb/7p/NnuctPH3g0IqKUuSQ==",
|
"integrity": "sha512-PzYWIKZeP+967WuKYXlTOhYBgGOvTRSfaKI89XnfJ0ansRAH7hDU45X+K+FZeI1Wb/7p/NnuctPH3g0IqKUuSQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^1.0.3",
|
"detect-libc": "^1.0.3",
|
||||||
"expand-template": "^2.0.3",
|
"expand-template": "^2.0.3",
|
||||||
@@ -17892,7 +18021,7 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||||
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
|
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -17901,13 +18030,13 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
|
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/prebuild-install/node_modules/are-we-there-yet": {
|
"node_modules/prebuild-install/node_modules/are-we-there-yet": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
|
||||||
"integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
|
"integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"delegates": "^1.0.0",
|
"delegates": "^1.0.0",
|
||||||
"readable-stream": "^2.0.6"
|
"readable-stream": "^2.0.6"
|
||||||
@@ -17917,7 +18046,7 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"detect-libc": "bin/detect-libc.js"
|
"detect-libc": "bin/detect-libc.js"
|
||||||
},
|
},
|
||||||
@@ -17929,7 +18058,7 @@
|
|||||||
"version": "2.7.4",
|
"version": "2.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||||
"integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
|
"integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aproba": "^1.0.3",
|
"aproba": "^1.0.3",
|
||||||
"console-control-strings": "^1.0.0",
|
"console-control-strings": "^1.0.0",
|
||||||
@@ -17945,7 +18074,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||||
"integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
|
"integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
},
|
},
|
||||||
@@ -17957,13 +18086,13 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/prebuild-install/node_modules/npmlog": {
|
"node_modules/prebuild-install/node_modules/npmlog": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"are-we-there-yet": "~1.1.2",
|
"are-we-there-yet": "~1.1.2",
|
||||||
"console-control-strings": "~1.1.0",
|
"console-control-strings": "~1.1.0",
|
||||||
@@ -17975,7 +18104,7 @@
|
|||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
"inherits": "~2.0.3",
|
"inherits": "~2.0.3",
|
||||||
@@ -17990,7 +18119,7 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
@@ -17999,7 +18128,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||||
"integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
|
"integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
@@ -18013,7 +18142,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
|
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -19337,9 +19466,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.18.0",
|
"version": "0.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
@@ -19372,6 +19501,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/send/node_modules/encodeurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/send/node_modules/ms": {
|
"node_modules/send/node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -19386,14 +19523,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
"version": "1.15.0",
|
"version": "1.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"send": "0.18.0"
|
"send": "0.19.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
@@ -19519,11 +19656,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||||
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
|
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.6",
|
"call-bind": "^1.0.7",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"get-intrinsic": "^1.2.4",
|
"get-intrinsic": "^1.2.4",
|
||||||
"object-inspect": "^1.13.1"
|
"object-inspect": "^1.13.1"
|
||||||
@@ -19536,9 +19673,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sift": {
|
"node_modules/sift": {
|
||||||
"version": "16.0.1",
|
"version": "17.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
|
||||||
"integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ=="
|
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/signal-exit": {
|
"node_modules/signal-exit": {
|
||||||
"version": "3.0.7",
|
"version": "3.0.7",
|
||||||
@@ -19568,7 +19706,7 @@
|
|||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
|
||||||
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
|
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"decompress-response": "^4.2.0",
|
"decompress-response": "^4.2.0",
|
||||||
"once": "^1.3.1",
|
"once": "^1.3.1",
|
||||||
@@ -19579,7 +19717,7 @@
|
|||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||||
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mimic-response": "^2.0.0"
|
"mimic-response": "^2.0.0"
|
||||||
},
|
},
|
||||||
@@ -19591,7 +19729,7 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
},
|
},
|
||||||
@@ -19732,15 +19870,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/smart-buffer": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6.0.0",
|
|
||||||
"npm": ">= 3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/snapdragon": {
|
"node_modules/snapdragon": {
|
||||||
"version": "0.8.2",
|
"version": "0.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||||
@@ -19860,19 +19989,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socks": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"ip": "^2.0.0",
|
|
||||||
"smart-buffer": "^4.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.13.0",
|
|
||||||
"npm": ">= 3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/sort-keys": {
|
"node_modules/sort-keys": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||||
@@ -19950,7 +20066,6 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||||
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"memory-pager": "^1.0.2"
|
"memory-pager": "^1.0.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "5.32.0",
|
"version": "5.36.0",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.10",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"apple-auth": "^1.0.9",
|
"apple-auth": "^1.0.9",
|
||||||
"babel-preset-env": "^1.7.0",
|
"babel-preset-env": "^1.7.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.3",
|
||||||
"bootstrap": "^4.6.2",
|
"bootstrap": "^4.6.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-session": "^2.0.0",
|
"cookie-session": "^2.0.0",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"eslint": "^8.55.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-habitrpg": "^6.2.3",
|
"eslint-config-habitrpg": "^6.2.3",
|
||||||
"eslint-plugin-mocha": "^5.0.0",
|
"eslint-plugin-mocha": "^5.0.0",
|
||||||
"express": "^4.19.2",
|
"express": "^4.21.1",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-validator": "^5.2.0",
|
"express-validator": "^5.2.0",
|
||||||
"firebase-admin": "^12.1.1",
|
"firebase-admin": "^12.1.1",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"method-override": "^3.0.0",
|
"method-override": "^3.0.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-recur": "^1.0.7",
|
"moment-recur": "^1.0.7",
|
||||||
"mongoose": "^7.6.3",
|
"mongoose": "^8.9.5",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"nconf": "^0.12.1",
|
"nconf": "^0.12.1",
|
||||||
"node-gcm": "^1.0.5",
|
"node-gcm": "^1.0.5",
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
"start:simple": "node ./website/server/index.js",
|
"start:simple": "node ./website/server/index.js",
|
||||||
"debug": "gulp nodemon --inspect",
|
"debug": "gulp nodemon --inspect",
|
||||||
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
"mongo:dev": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data --number 1 --quiet",
|
||||||
|
"mongo:test": "run-rs -v 5.0.23 -l ubuntu1804 --keep --dbpath mongodb-data-testing --number 1 --quiet",
|
||||||
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
"postinstall": "git config --global url.\"https://\".insteadOf git:// && gulp build && cd website/client && npm install",
|
||||||
"apidoc": "gulp apidoc",
|
"apidoc": "gulp apidoc",
|
||||||
"heroku-postbuild": ".heroku/report_deploy.sh"
|
"heroku-postbuild": ".heroku/report_deploy.sh"
|
||||||
|
|||||||
@@ -2,13 +2,22 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import requireAgain from 'require-again';
|
import requireAgain from 'require-again';
|
||||||
import { recoverCron, cron } from '../../../../website/server/libs/cron';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
import {
|
||||||
|
generateRes,
|
||||||
|
generateReq,
|
||||||
|
generateTodo,
|
||||||
|
generateDaily,
|
||||||
|
} from '../../../helpers/api-unit.helper';
|
||||||
|
import { cron, cronWrapper } from '../../../../website/server/libs/cron';
|
||||||
import { model as User } from '../../../../website/server/models/user';
|
import { model as User } from '../../../../website/server/models/user';
|
||||||
import * as Tasks from '../../../../website/server/models/task';
|
import * as Tasks from '../../../../website/server/models/task';
|
||||||
import common from '../../../../website/common';
|
import common from '../../../../website/common';
|
||||||
import * as analytics from '../../../../website/server/libs/analyticsService';
|
import * as analytics from '../../../../website/server/libs/analyticsService';
|
||||||
|
import { model as Group } from '../../../../website/server/models/group';
|
||||||
|
|
||||||
// const scoreTask = common.ops.scoreTask;
|
const CRON_TIMEOUT_WAIT = new Date(5 * 60 * 1000).getTime();
|
||||||
|
const CRON_TIMEOUT_UNIT = new Date(60 * 1000).getTime();
|
||||||
|
|
||||||
const pathToCronLib = '../../../../website/server/libs/cron';
|
const pathToCronLib = '../../../../website/server/libs/cron';
|
||||||
|
|
||||||
@@ -1200,7 +1209,7 @@ describe('cron', async () => {
|
|||||||
it('increments perfect day achievement if all (at least 1) due dailies were completed', async () => {
|
it('increments perfect day achievement if all (at least 1) due dailies were completed', async () => {
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].isDue = true;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed, analytics,
|
||||||
@@ -1212,7 +1221,7 @@ describe('cron', async () => {
|
|||||||
it('does not increment perfect day achievement if no due dailies', async () => {
|
it('does not increment perfect day achievement if no due dailies', async () => {
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).add({ days: 1 });
|
tasksByType.dailys[0].isDue = false;
|
||||||
|
|
||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed, analytics,
|
||||||
@@ -1224,7 +1233,7 @@ describe('cron', async () => {
|
|||||||
it('gives perfect day buff if all (at least 1) due dailies were completed', async () => {
|
it('gives perfect day buff if all (at least 1) due dailies were completed', async () => {
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].isDue = true;
|
||||||
|
|
||||||
const previousBuffs = user.stats.buffs.toObject();
|
const previousBuffs = user.stats.buffs.toObject();
|
||||||
|
|
||||||
@@ -1242,7 +1251,7 @@ describe('cron', async () => {
|
|||||||
user.preferences.sleep = true;
|
user.preferences.sleep = true;
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
tasksByType.dailys[0].isDue = true;
|
||||||
|
|
||||||
const previousBuffs = user.stats.buffs.toObject();
|
const previousBuffs = user.stats.buffs.toObject();
|
||||||
|
|
||||||
@@ -1259,7 +1268,7 @@ describe('cron', async () => {
|
|||||||
it('clears buffs if user does not have a perfect day (no due dailys)', async () => {
|
it('clears buffs if user does not have a perfect day (no due dailys)', async () => {
|
||||||
daysMissed = 1;
|
daysMissed = 1;
|
||||||
tasksByType.dailys[0].completed = true;
|
tasksByType.dailys[0].completed = true;
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).add({ days: 1 });
|
tasksByType.dailys[0].isDue = false;
|
||||||
|
|
||||||
user.stats.buffs = {
|
user.stats.buffs = {
|
||||||
str: 1,
|
str: 1,
|
||||||
@@ -1488,78 +1497,6 @@ describe('cron', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('notifications', async () => {
|
|
||||||
it('adds a user notification', async () => {
|
|
||||||
const mpBefore = user.stats.mp;
|
|
||||||
tasksByType.dailys[0].completed = true;
|
|
||||||
|
|
||||||
const statsComputedRes = common.statsComputed(user);
|
|
||||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
|
||||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
|
||||||
|
|
||||||
daysMissed = 1;
|
|
||||||
const hpBefore = user.stats.hp;
|
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
|
||||||
|
|
||||||
await cron({
|
|
||||||
user, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(user.notifications.length).to.be.greaterThan(0);
|
|
||||||
expect(user.notifications[1].type).to.equal('CRON');
|
|
||||||
expect(user.notifications[1].data).to.eql({
|
|
||||||
hp: user.stats.hp - hpBefore,
|
|
||||||
mp: user.stats.mp - mpBefore,
|
|
||||||
});
|
|
||||||
|
|
||||||
common.statsComputed.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('condenses multiple notifications into one', async () => {
|
|
||||||
const mpBefore1 = user.stats.mp;
|
|
||||||
tasksByType.dailys[0].completed = true;
|
|
||||||
|
|
||||||
const statsComputedRes = common.statsComputed(user);
|
|
||||||
const stubbedStatsComputed = sinon.stub(common, 'statsComputed');
|
|
||||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
|
||||||
|
|
||||||
daysMissed = 1;
|
|
||||||
const hpBefore1 = user.stats.hp;
|
|
||||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
|
||||||
|
|
||||||
await cron({
|
|
||||||
user, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(user.notifications.length).to.be.greaterThan(0);
|
|
||||||
expect(user.notifications[1].type).to.equal('CRON');
|
|
||||||
expect(user.notifications[1].data).to.eql({
|
|
||||||
hp: user.stats.hp - hpBefore1,
|
|
||||||
mp: user.stats.mp - mpBefore1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const notifsBefore2 = user.notifications.length;
|
|
||||||
const hpBefore2 = user.stats.hp;
|
|
||||||
const mpBefore2 = user.stats.mp;
|
|
||||||
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
|
|
||||||
await cron({
|
|
||||||
user, tasksByType, daysMissed, analytics,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(user.notifications.length - notifsBefore2).to.equal(0);
|
|
||||||
expect(user.notifications[0].type).to.not.equal('CRON');
|
|
||||||
expect(user.notifications[1].type).to.equal('CRON');
|
|
||||||
expect(user.notifications[1].data).to.eql({
|
|
||||||
hp: user.stats.hp - hpBefore2 - (hpBefore2 - hpBefore1),
|
|
||||||
mp: user.stats.mp - mpBefore2 - (mpBefore2 - mpBefore1),
|
|
||||||
});
|
|
||||||
expect(user.notifications[0].type).to.not.equal('CRON');
|
|
||||||
common.statsComputed.restore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('private messages', async () => {
|
describe('private messages', async () => {
|
||||||
let lastMessageId;
|
let lastMessageId;
|
||||||
|
|
||||||
@@ -1606,7 +1543,7 @@ describe('cron', async () => {
|
|||||||
await cron({
|
await cron({
|
||||||
user, tasksByType, daysMissed, analytics,
|
user, tasksByType, daysMissed, analytics,
|
||||||
});
|
});
|
||||||
expect(user.notifications.length).to.be.greaterThan(1);
|
expect(user.notifications.length).to.eql(1);
|
||||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1820,64 +1757,258 @@ describe('cron', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('recoverCron', async () => {
|
describe('cron wrapper', () => {
|
||||||
let locals; let status; let
|
let res; let
|
||||||
execStub;
|
req;
|
||||||
|
let user;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
execStub = sandbox.stub();
|
res = generateRes();
|
||||||
sandbox.stub(User, 'findOne').returns({ exec: execStub });
|
req = generateReq();
|
||||||
|
user = await res.locals.user.save();
|
||||||
status = { times: 0 };
|
res.analytics = analytics;
|
||||||
locals = {
|
|
||||||
user: new User({
|
|
||||||
auth: {
|
|
||||||
local: {
|
|
||||||
username: 'username',
|
|
||||||
lowerCaseUsername: 'username',
|
|
||||||
email: 'email@example.com',
|
|
||||||
salt: 'salt',
|
|
||||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(() => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if user cannot be found', async () => {
|
it('calls next when user is not attached', async () => {
|
||||||
execStub.returns(Promise.resolve(null));
|
res.locals.user = null;
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls next when days have not been missed', async () => {
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear todos older than 30 days for free users', async () => {
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
const task = generateTodo(user);
|
||||||
|
task.dateCompleted = moment(new Date()).subtract({ days: 31 });
|
||||||
|
task.completed = true;
|
||||||
|
await task.save();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
const taskRes = await Tasks.Task.findOne({ _id: task._id });
|
||||||
|
expect(taskRes).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not clear todos older than 30 days for subscribed users', async () => {
|
||||||
|
user.purchased.plan.customerId = 'subscribedId';
|
||||||
|
user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY');
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
const task = generateTodo(user);
|
||||||
|
task.dateCompleted = moment(new Date()).subtract({ days: 31 });
|
||||||
|
task.completed = true;
|
||||||
|
await Promise.all([task.save(), user.save()]);
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
const taskRes = await Tasks.Task.findOne({ _id: task._id });
|
||||||
|
expect(taskRes).to.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear todos older than 90 days for subscribed users', async () => {
|
||||||
|
user.purchased.plan.customerId = 'subscribedId';
|
||||||
|
user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY');
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
|
||||||
|
const task = generateTodo(user);
|
||||||
|
task.dateCompleted = moment(new Date()).subtract({ days: 91 });
|
||||||
|
task.completed = true;
|
||||||
|
await task.save();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
const taskRes = await Tasks.Task.findOne({ _id: task._id });
|
||||||
|
expect(taskRes).to.not.exist;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call next if user was not modified after cron', async () => {
|
||||||
|
const hpBefore = user.stats.hp;
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
expect(hpBefore).to.equal(user.stats.hp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs cron if previous cron was incomplete', async () => {
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 1 });
|
||||||
|
user.auth.timestamps.loggedin = moment(new Date()).subtract({ days: 4 });
|
||||||
|
const now = new Date();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
expect(moment(now).isSame(user.lastCron, 'day'));
|
||||||
|
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates user.auth.timestamps.loggedin and lastCron', async () => {
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
const now = new Date();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
expect(moment(now).isSame(user.lastCron, 'day'));
|
||||||
|
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does damage for missing dailies', async () => {
|
||||||
|
const hpBefore = user.stats.hp;
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
const daily = generateDaily(user);
|
||||||
|
daily.startDate = moment(new Date()).subtract({ days: 2 });
|
||||||
|
await daily.save();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
const updatedUser = await User.findOne({ _id: user._id });
|
||||||
|
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates tasks', async () => {
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
const todo = generateTodo(user);
|
||||||
|
const todoValueBefore = todo.value;
|
||||||
|
await Promise.all([todo.save(), user.save()]);
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
const todoFound = await Tasks.Task.findOne({ _id: todo._id });
|
||||||
|
expect(todoFound.value).to.be.lessThan(todoValueBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates large number of tasks', async () => {
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
const todo = generateTodo(user);
|
||||||
|
const todoValueBefore = todo.value;
|
||||||
|
const start = new Date();
|
||||||
|
const saves = [todo.save(), user.save()];
|
||||||
|
for (let i = 0; i < 200; i += 1) {
|
||||||
|
const newTodo = generateTodo(user);
|
||||||
|
newTodo.value = i;
|
||||||
|
saves.push(newTodo.save());
|
||||||
|
}
|
||||||
|
await Promise.all(saves);
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
const duration = new Date() - start;
|
||||||
|
expect(duration).to.be.lessThan(1000);
|
||||||
|
const todoFound = await Tasks.Task.findOne({ _id: todo._id });
|
||||||
|
expect(moment(start).isSame(user.lastCron, 'day'));
|
||||||
|
expect(moment(start).isSame(user.auth.timestamps.loggedin, 'day'));
|
||||||
|
expect(todoFound.value).to.be.lessThan(todoValueBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails entire cron if one task is failing', async () => {
|
||||||
|
const lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
user.lastCron = lastCron;
|
||||||
|
const todo = generateTodo(user);
|
||||||
|
const todoValueBefore = todo.value;
|
||||||
|
const badTodo = generateTodo(user);
|
||||||
|
badTodo.text = 'bad todo';
|
||||||
|
badTodo.attribute = 'bad';
|
||||||
|
await Promise.all([badTodo.save({ validateBeforeSave: false }), todo.save(), user.save()]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await recoverCron(status, locals);
|
await cronWrapper(req, res);
|
||||||
throw new Error('no exception when user cannot be found');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err.message).to.eql(`User ${locals.user._id} not found while recovering.`);
|
expect(err).to.exist;
|
||||||
|
}
|
||||||
|
const todoFound = await Tasks.Task.findOne({ _id: todo._id });
|
||||||
|
expect(moment(lastCron).isSame(user.lastCron, 'day'));
|
||||||
|
expect(todoFound.value).to.be.equal(todoValueBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies quest progress', async () => {
|
||||||
|
const hpBefore = user.stats.hp;
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
const daily = generateDaily(user);
|
||||||
|
daily.startDate = moment(new Date()).subtract({ days: 2 });
|
||||||
|
await daily.save();
|
||||||
|
|
||||||
|
const questKey = 'dilatory';
|
||||||
|
user.party.quest.key = questKey;
|
||||||
|
|
||||||
|
const party = new Group({
|
||||||
|
type: 'party',
|
||||||
|
name: generateUUID(),
|
||||||
|
leader: user._id,
|
||||||
|
});
|
||||||
|
party.quest.members[user._id] = true;
|
||||||
|
party.quest.key = questKey;
|
||||||
|
await party.save();
|
||||||
|
|
||||||
|
user.party._id = party._id;
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
party.startQuest(user);
|
||||||
|
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
const updatedUser = await User.findOne({ _id: user._id });
|
||||||
|
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cronSignature less than 5 minutes ago should error', async () => {
|
||||||
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
const now = new Date();
|
||||||
|
await User.updateOne({
|
||||||
|
_id: user._id,
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT + CRON_TIMEOUT_UNIT,
|
||||||
|
},
|
||||||
|
}).exec();
|
||||||
|
await user.save();
|
||||||
|
try {
|
||||||
|
await cronWrapper(req, res);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).to.exist;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('increases status.times count and reruns up to 4 times', async () => {
|
it('cronSignature longer than an hour ago should allow cron', async () => {
|
||||||
execStub.returns(Promise.resolve({ _cronSignature: 'RUNNING_CRON' }));
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
execStub.onCall(4).returns(Promise.resolve({ _cronSignature: 'NOT_RUNNING' }));
|
const now = new Date();
|
||||||
|
await User.updateOne({
|
||||||
|
_id: user._id,
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT - CRON_TIMEOUT_UNIT,
|
||||||
|
},
|
||||||
|
}).exec();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
await recoverCron(status, locals);
|
await cronWrapper(req, res);
|
||||||
|
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
|
||||||
expect(status.times).to.eql(4);
|
expect(user._cronSignature).to.be.equal('NOT_RUNNING');
|
||||||
expect(locals.user).to.eql({ _cronSignature: 'NOT_RUNNING' });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if recoverCron runs 5 times', async () => {
|
it('cron should not run more than once', async () => {
|
||||||
execStub.returns(Promise.resolve({ _cronSignature: 'RUNNING_CRON' }));
|
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
||||||
|
await user.save();
|
||||||
|
|
||||||
try {
|
const result = await Promise.allSettled([
|
||||||
await recoverCron(status, locals);
|
cronWrapper(req, res),
|
||||||
throw new Error('no exception when recoverCron runs 5 times');
|
cronWrapper(req, res),
|
||||||
} catch (err) {
|
new Promise((resolve, reject) => {
|
||||||
expect(status.times).to.eql(5);
|
setTimeout(async () => {
|
||||||
expect(err.message).to.eql(`Impossible to recover from cron for user ${locals.user._id}.`);
|
try {
|
||||||
}
|
const runResult = await cronWrapper(req, res);
|
||||||
|
if (runResult !== null) {
|
||||||
|
reject(new Error('cron ran more than once'));
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.filter(r => r.status === 'fulfilled')).to.have.lengthOf(2);
|
||||||
|
expect(result.filter(r => r.status === 'rejected')).to.have.lengthOf(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -171,23 +171,23 @@ describe('emails', () => {
|
|||||||
expect(got.post).not.to.be.called;
|
expect(got.post).not.to.be.called;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws error when mail target is only a string', () => {
|
it('throws error when mail target is only a string', async () => {
|
||||||
const emailType = 'an email type';
|
const emailType = 'an email type';
|
||||||
const mailingInfo = 'my email';
|
const mailingInfo = 'my email';
|
||||||
|
|
||||||
expect(sendTxn(mailingInfo, emailType)).to.throw;
|
await expect(sendTxn(mailingInfo, emailType)).to.be.rejectedWith('Argument Error mailingInfoArray: does not contain email or _id');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws error when mail target has no _id or email', () => {
|
it('throws error when mail target has no _id or email', async () => {
|
||||||
const emailType = 'an email type';
|
const emailType = 'an email type';
|
||||||
const mailingInfo = {
|
const mailingInfo = {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(sendTxn(mailingInfo, emailType)).to.throw;
|
await expect(sendTxn(mailingInfo, emailType)).to.be.rejectedWith('Argument Error mailingInfoArray: does not contain email or _id');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws error when variables not an array', () => {
|
it('throws error when variables not an array', async () => {
|
||||||
const emailType = 'an email type';
|
const emailType = 'an email type';
|
||||||
const mailingInfo = {
|
const mailingInfo = {
|
||||||
name: 'my name',
|
name: 'my name',
|
||||||
@@ -195,9 +195,10 @@ describe('emails', () => {
|
|||||||
};
|
};
|
||||||
const variables = {};
|
const variables = {};
|
||||||
|
|
||||||
expect(sendTxn(mailingInfo, emailType, variables)).to.throw;
|
await expect(sendTxn(mailingInfo, emailType, variables)).to.be.rejectedWith('Argument Error variables: is not an array');
|
||||||
});
|
});
|
||||||
it('throws error when variables array not contain name/content', () => {
|
|
||||||
|
it('throws error when variables array not contain name/content', async () => {
|
||||||
const emailType = 'an email type';
|
const emailType = 'an email type';
|
||||||
const mailingInfo = {
|
const mailingInfo = {
|
||||||
name: 'my name',
|
name: 'my name',
|
||||||
@@ -209,8 +210,9 @@ describe('emails', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(sendTxn(mailingInfo, emailType, variables)).to.throw;
|
await expect(sendTxn(mailingInfo, emailType, variables)).to.be.rejectedWith('Argument Error variables: does not contain name or content');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws no error when variables array contain name but no content', () => {
|
it('throws no error when variables array contain name but no content', () => {
|
||||||
const emailType = 'an email type';
|
const emailType = 'an email type';
|
||||||
const mailingInfo = {
|
const mailingInfo = {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import nconf from 'nconf';
|
|
||||||
import requireAgain from 'require-again';
|
import requireAgain from 'require-again';
|
||||||
|
|
||||||
const pathToMongoLib = '../../../../website/server/libs/mongodb';
|
const pathToMongoLib = '../../../../website/server/libs/mongodb';
|
||||||
@@ -29,22 +28,4 @@ describe('mongodb', () => {
|
|||||||
expect(string).to.equal('mongodb://hostname:3030');
|
expect(string).to.equal('mongodb://hostname:3030');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getDefaultConnectionOptions', () => {
|
|
||||||
it('returns development config when IS_PROD is false', () => {
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
|
|
||||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
|
||||||
|
|
||||||
const options = mongoLibOverride.getDefaultConnectionOptions();
|
|
||||||
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns production config when IS_PROD is true', () => {
|
|
||||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
|
||||||
const mongoLibOverride = requireAgain(pathToMongoLib);
|
|
||||||
|
|
||||||
const options = mongoLibOverride.getDefaultConnectionOptions();
|
|
||||||
expect(options).to.have.all.keys(['useNewUrlParser', 'useUnifiedTopology']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,332 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import { v4 as generateUUID } from 'uuid';
|
|
||||||
import {
|
|
||||||
generateRes,
|
|
||||||
generateReq,
|
|
||||||
generateTodo,
|
|
||||||
generateDaily,
|
|
||||||
} from '../../../helpers/api-unit.helper';
|
|
||||||
import cronMiddleware from '../../../../website/server/middlewares/cron';
|
|
||||||
import { model as User } from '../../../../website/server/models/user';
|
|
||||||
import { model as Group } from '../../../../website/server/models/group';
|
|
||||||
import * as Tasks from '../../../../website/server/models/task';
|
|
||||||
import * as analyticsService from '../../../../website/server/libs/analyticsService';
|
|
||||||
import * as cronLib from '../../../../website/server/libs/cron';
|
|
||||||
|
|
||||||
const CRON_TIMEOUT_WAIT = new Date(60 * 60 * 1000).getTime();
|
|
||||||
const CRON_TIMEOUT_UNIT = new Date(60 * 1000).getTime();
|
|
||||||
|
|
||||||
describe('cron middleware', () => {
|
|
||||||
let res; let
|
|
||||||
req;
|
|
||||||
let user;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
res = generateRes();
|
|
||||||
req = generateReq();
|
|
||||||
user = await res.locals.user.save();
|
|
||||||
res.analytics = analyticsService;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls next when user is not attached', done => {
|
|
||||||
res.locals.user = null;
|
|
||||||
cronMiddleware(req, res, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls next when days have not been missed', done => {
|
|
||||||
cronMiddleware(req, res, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear todos older than 30 days for free users', async () => {
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
const task = generateTodo(user);
|
|
||||||
task.dateCompleted = moment(new Date()).subtract({ days: 31 });
|
|
||||||
task.completed = true;
|
|
||||||
await task.save();
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
|
|
||||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
|
||||||
expect(foundTask).to.not.exist;
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not clear todos older than 30 days for subscribed users', async () => {
|
|
||||||
user.purchased.plan.customerId = 'subscribedId';
|
|
||||||
user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY');
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
const task = generateTodo(user);
|
|
||||||
task.dateCompleted = moment(new Date()).subtract({ days: 31 });
|
|
||||||
task.completed = true;
|
|
||||||
await task.save();
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
|
||||||
expect(foundTask).to.exist;
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear todos older than 90 days for subscribed users', async () => {
|
|
||||||
user.purchased.plan.customerId = 'subscribedId';
|
|
||||||
user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY');
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
|
|
||||||
const task = generateTodo(user);
|
|
||||||
task.dateCompleted = moment(new Date()).subtract({ days: 91 });
|
|
||||||
task.completed = true;
|
|
||||||
await task.save();
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
Tasks.Task.findOne({ _id: task }).then(foundTask => {
|
|
||||||
expect(foundTask).to.not.exist;
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call next if user was not modified after cron', async () => {
|
|
||||||
const hpBefore = user.stats.hp;
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
expect(hpBefore).to.equal(user.stats.hp);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('runs cron if previous cron was incomplete', async () => {
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 1 });
|
|
||||||
user.auth.timestamps.loggedin = moment(new Date()).subtract({ days: 4 });
|
|
||||||
const now = new Date();
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
expect(moment(now).isSame(user.lastCron, 'day'));
|
|
||||||
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates user.auth.timestamps.loggedin and lastCron', async () => {
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
const now = new Date();
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
expect(moment(now).isSame(user.lastCron, 'day'));
|
|
||||||
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does damage for missing dailies', async () => {
|
|
||||||
const hpBefore = user.stats.hp;
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
const daily = generateDaily(user);
|
|
||||||
daily.startDate = moment(new Date()).subtract({ days: 2 });
|
|
||||||
await daily.save();
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
return User.findOne({ _id: user._id }).then(updatedUser => {
|
|
||||||
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates tasks', async () => {
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
const todo = generateTodo(user);
|
|
||||||
const todoValueBefore = todo.value;
|
|
||||||
await Promise.all([todo.save(), user.save()]);
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
return Tasks.Task.findOne({ _id: todo._id }).then(todoFound => {
|
|
||||||
expect(todoFound.value).to.be.lessThan(todoValueBefore);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('applies quest progress', async () => {
|
|
||||||
const hpBefore = user.stats.hp;
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
const daily = generateDaily(user);
|
|
||||||
daily.startDate = moment(new Date()).subtract({ days: 2 });
|
|
||||||
await daily.save();
|
|
||||||
|
|
||||||
const questKey = 'dilatory';
|
|
||||||
user.party.quest.key = questKey;
|
|
||||||
|
|
||||||
const party = new Group({
|
|
||||||
type: 'party',
|
|
||||||
name: generateUUID(),
|
|
||||||
leader: user._id,
|
|
||||||
});
|
|
||||||
party.quest.members[user._id] = true;
|
|
||||||
party.quest.key = questKey;
|
|
||||||
await party.save();
|
|
||||||
|
|
||||||
user.party._id = party._id;
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
party.startQuest(user);
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
return User.findOne({ _id: user._id }).then(updatedUser => {
|
|
||||||
expect(updatedUser.stats.hp).to.be.lessThan(hpBefore);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('recovers from failed cron and does not error when user is already cronning', async () => {
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
const updatedUser = user.toObject();
|
|
||||||
updatedUser.matchedCount = 0;
|
|
||||||
|
|
||||||
sandbox.spy(cronLib, 'recoverCron');
|
|
||||||
|
|
||||||
sandbox.stub(User, 'updateOne')
|
|
||||||
.withArgs({
|
|
||||||
_id: user._id,
|
|
||||||
$or: [
|
|
||||||
{ _cronSignature: 'NOT_RUNNING' },
|
|
||||||
{ _cronSignature: { $lt: sinon.match.number } },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
.returns({
|
|
||||||
exec () {
|
|
||||||
return Promise.resolve(updatedUser);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
expect(cronLib.recoverCron).to.be.calledOnce;
|
|
||||||
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cronSignature less than an hour ago should error', async () => {
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
const now = new Date();
|
|
||||||
await User.updateOne({
|
|
||||||
_id: user._id,
|
|
||||||
}, {
|
|
||||||
$set: {
|
|
||||||
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT + CRON_TIMEOUT_UNIT,
|
|
||||||
},
|
|
||||||
}).exec();
|
|
||||||
await user.save();
|
|
||||||
const expectedErrMessage = `Impossible to recover from cron for user ${user._id}.`;
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (!err) return reject(new Error('Cron should have failed.'));
|
|
||||||
expect(err.message).to.be.equal(expectedErrMessage);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cronSignature longer than an hour ago should allow cron', async () => {
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
const now = new Date();
|
|
||||||
await User.updateOne({
|
|
||||||
_id: user._id,
|
|
||||||
}, {
|
|
||||||
$set: {
|
|
||||||
_cronSignature: now.getTime() - CRON_TIMEOUT_WAIT - CRON_TIMEOUT_UNIT,
|
|
||||||
},
|
|
||||||
}).exec();
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day'));
|
|
||||||
expect(user._cronSignature).to.be.equal('NOT_RUNNING');
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cron should not run more than once', async () => {
|
|
||||||
user.lastCron = moment(new Date()).subtract({ days: 2 });
|
|
||||||
await user.save();
|
|
||||||
|
|
||||||
sandbox.spy(cronLib, 'cron');
|
|
||||||
|
|
||||||
await Promise.all([new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
}), new Promise((resolve, reject) => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
}), new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
cronMiddleware(req, res, err => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
}, 400);
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(cronLib.cron).to.be.calledOnce;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -59,7 +59,7 @@ describe('POST /debug/jump-time', () => {
|
|||||||
expect(resultDate.getDate()).to.eql(today.getDate());
|
expect(resultDate.getDate()).to.eql(today.getDate());
|
||||||
expect(resultDate.getMonth()).to.eql(today.getMonth());
|
expect(resultDate.getMonth()).to.eql(today.getMonth());
|
||||||
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
|
expect(resultDate.getFullYear()).to.eql(today.getFullYear());
|
||||||
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 355 })).time);
|
const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 365 })).time);
|
||||||
expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1);
|
expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe('GET /heroes/:heroId', () => {
|
|||||||
const heroFields = [
|
const heroFields = [
|
||||||
'_id', 'id', 'auth', 'balance', 'contributor', 'flags', 'items',
|
'_id', 'id', 'auth', 'balance', 'contributor', 'flags', 'items',
|
||||||
'lastCron', 'party', 'preferences', 'profile', 'purchased', 'secret', 'achievements',
|
'lastCron', 'party', 'preferences', 'profile', 'purchased', 'secret', 'achievements',
|
||||||
|
'stats',
|
||||||
];
|
];
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ describe('PUT /heroes/:heroId', () => {
|
|||||||
const heroFields = [
|
const heroFields = [
|
||||||
'_id', 'auth', 'balance', 'contributor', 'flags', 'items', 'lastCron',
|
'_id', 'auth', 'balance', 'contributor', 'flags', 'items', 'lastCron',
|
||||||
'party', 'preferences', 'profile', 'purchased', 'secret', 'permissions', 'achievements',
|
'party', 'preferences', 'profile', 'purchased', 'secret', 'permissions', 'achievements',
|
||||||
|
'stats',
|
||||||
];
|
];
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
@@ -60,12 +61,12 @@ describe('PUT /heroes/:heroId', () => {
|
|||||||
expect(heroRes.profile).to.have.all.keys(['name']);
|
expect(heroRes.profile).to.have.all.keys(['name']);
|
||||||
|
|
||||||
// test response values
|
// test response values
|
||||||
expect(heroRes.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
|
expect(heroRes.balance).to.equal(3 + 2.5); // 3+2.5 for first contrib level
|
||||||
expect(heroRes.contributor.level).to.equal(1);
|
expect(heroRes.contributor.level).to.equal(1);
|
||||||
expect(heroRes.purchased.ads).to.equal(true);
|
expect(heroRes.purchased.ads).to.equal(true);
|
||||||
// test hero values
|
// test hero values
|
||||||
await hero.sync();
|
await hero.sync();
|
||||||
expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
|
expect(hero.balance).to.equal(3 + 2.5); // 3+2.5 for first contrib level
|
||||||
expect(hero.contributor.level).to.equal(1);
|
expect(hero.contributor.level).to.equal(1);
|
||||||
expect(hero.purchased.ads).to.equal(true);
|
expect(hero.purchased.ads).to.equal(true);
|
||||||
expect(hero.auth.blocked).to.equal(prevBlockState);
|
expect(hero.auth.blocked).to.equal(prevBlockState);
|
||||||
@@ -136,12 +137,12 @@ describe('PUT /heroes/:heroId', () => {
|
|||||||
expect(heroRes.profile).to.have.all.keys(['name']);
|
expect(heroRes.profile).to.have.all.keys(['name']);
|
||||||
|
|
||||||
// test response values
|
// test response values
|
||||||
expect(heroRes.balance).to.equal(1); // 0+1 for sixth contrib level
|
expect(heroRes.balance).to.equal(15); // 0+15 for sixth contrib level
|
||||||
expect(heroRes.contributor.level).to.equal(6);
|
expect(heroRes.contributor.level).to.equal(6);
|
||||||
expect(heroRes.items.pets['Dragon-Hydra']).to.equal(5);
|
expect(heroRes.items.pets['Dragon-Hydra']).to.equal(5);
|
||||||
// test hero values
|
// test hero values
|
||||||
await hero.sync();
|
await hero.sync();
|
||||||
expect(hero.balance).to.equal(1); // 0+1 for sixth contrib level
|
expect(hero.balance).to.equal(15); // 0+15 for sixth contrib level
|
||||||
expect(hero.contributor.level).to.equal(6);
|
expect(hero.contributor.level).to.equal(6);
|
||||||
expect(hero.items.pets['Dragon-Hydra']).to.equal(5);
|
expect(hero.items.pets['Dragon-Hydra']).to.equal(5);
|
||||||
});
|
});
|
||||||
|
|||||||
56
test/api/v3/integration/members/GET-members_username.test.js
Normal file
56
test/api/v3/integration/members/GET-members_username.test.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
import common from '../../../../../website/common';
|
||||||
|
|
||||||
|
describe('GET /members/username/:username', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates req.params.username', async () => {
|
||||||
|
await expect(user.get('/members/username/')).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('invalidReqParams'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a member\'s public data only', async () => {
|
||||||
|
// make sure user has all the fields that can be returned by the getMember call
|
||||||
|
const member = await generateUser({
|
||||||
|
contributor: { level: 1 },
|
||||||
|
backer: { tier: 3 },
|
||||||
|
preferences: {
|
||||||
|
costume: false,
|
||||||
|
background: 'volcano',
|
||||||
|
},
|
||||||
|
secret: {
|
||||||
|
text: 'Clark Kent',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const memberRes = await user.get(`/members/username/${member.auth.local.username}`);
|
||||||
|
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
|
||||||
|
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
|
||||||
|
'backer', 'contributor', 'auth', 'items', 'inbox', 'loginIncentives', 'flags',
|
||||||
|
]);
|
||||||
|
expect(Object.keys(memberRes.auth)).to.eql(['local', 'timestamps']);
|
||||||
|
expect(Object.keys(memberRes.preferences).sort()).to.eql([
|
||||||
|
'size', 'hair', 'skin', 'shirt',
|
||||||
|
'chair', 'costume', 'sleep', 'background', 'tasks', 'disableClasses',
|
||||||
|
].sort());
|
||||||
|
|
||||||
|
expect(memberRes.stats.maxMP).to.exist;
|
||||||
|
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
|
||||||
|
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
|
||||||
|
expect(memberRes.inbox.optOut).to.exist;
|
||||||
|
expect(memberRes.inbox.canReceive).to.exist;
|
||||||
|
expect(memberRes.inbox.messages).to.not.exist;
|
||||||
|
expect(memberRes.secret).to.not.exist;
|
||||||
|
|
||||||
|
expect(memberRes.blocks).to.not.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -101,34 +101,6 @@ describe('GET /tasks/user', () => {
|
|||||||
expect(allCompletedTodos[allCompletedTodos.length - 1].text).to.equal('todo to complete 2');
|
expect(allCompletedTodos[allCompletedTodos.length - 1].text).to.equal('todo to complete 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns only some completed todos if req.query.type is "completedTodos" or "_allCompletedTodos"', async () => {
|
|
||||||
const LIMIT = 30;
|
|
||||||
const numberOfTodos = LIMIT + 1;
|
|
||||||
const todosInput = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < numberOfTodos; i += 1) {
|
|
||||||
todosInput[i] = { text: `todo to complete ${i}`, type: 'todo' };
|
|
||||||
}
|
|
||||||
const todos = await user.post('/tasks/user', todosInput);
|
|
||||||
await user.sync();
|
|
||||||
const initialTodoCount = user.tasksOrder.todos.length;
|
|
||||||
|
|
||||||
for (let i = 0; i < numberOfTodos; i += 1) {
|
|
||||||
const id = todos[i]._id;
|
|
||||||
|
|
||||||
await user.post(`/tasks/${id}/score/up`); // eslint-disable-line no-await-in-loop
|
|
||||||
}
|
|
||||||
await user.sync();
|
|
||||||
|
|
||||||
expect(user.tasksOrder.todos.length).to.equal(initialTodoCount - numberOfTodos);
|
|
||||||
|
|
||||||
const completedTodos = await user.get('/tasks/user?type=completedTodos');
|
|
||||||
expect(completedTodos.length).to.equal(LIMIT);
|
|
||||||
|
|
||||||
const allCompletedTodos = await user.get('/tasks/user?type=_allCompletedTodos');
|
|
||||||
expect(allCompletedTodos.length).to.equal(numberOfTodos);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns dailies with isDue for the date specified', async () => {
|
it('returns dailies with isDue for the date specified', async () => {
|
||||||
// @TODO Add required format
|
// @TODO Add required format
|
||||||
const startDate = moment().subtract('1', 'days').toISOString();
|
const startDate = moment().subtract('1', 'days').toISOString();
|
||||||
|
|||||||
104
test/api/v4/inbox/POST-inbox_message_like.test.js
Normal file
104
test/api/v4/inbox/POST-inbox_message_like.test.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import find from 'lodash/find';
|
||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../helpers/api-integration/v4';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the messages array if the uniqueMessageId has the like flag
|
||||||
|
* @param {InboxMessage[]} messages
|
||||||
|
* @param {String} uniqueMessageId
|
||||||
|
* @param {String} userId
|
||||||
|
* @param {Boolean} likeStatus
|
||||||
|
*/
|
||||||
|
function expectMessagesLikeStatus (messages, uniqueMessageId, userId, likeStatus) {
|
||||||
|
const messageToCheck = find(messages, { uniqueMessageId });
|
||||||
|
|
||||||
|
expect(messageToCheck.likes[userId]).to.equal(likeStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line mocha/no-exclusive-tests
|
||||||
|
describe('POST /inbox/like-private-message/:messageId', () => {
|
||||||
|
let userToSendMessage;
|
||||||
|
const getLikeUrl = messageId => `/inbox/like-private-message/${messageId}`;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
userToSendMessage = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error when private message is not found', async () => {
|
||||||
|
await expect(userToSendMessage.post(getLikeUrl('some-unknown-id')))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('messageGroupChatNotFound'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('likes a message', async () => {
|
||||||
|
const receiver = await generateUser();
|
||||||
|
|
||||||
|
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
|
||||||
|
message: 'some message :)',
|
||||||
|
toUserId: receiver._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { uniqueMessageId } = sentMessageResult.message;
|
||||||
|
|
||||||
|
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
|
||||||
|
expect(likeResult.likes[receiver._id]).to.equal(true);
|
||||||
|
|
||||||
|
const senderMessages = await userToSendMessage.get('/inbox/messages');
|
||||||
|
|
||||||
|
expectMessagesLikeStatus(senderMessages, uniqueMessageId, receiver._id, true);
|
||||||
|
|
||||||
|
const receiversMessages = await receiver.get('/inbox/messages');
|
||||||
|
|
||||||
|
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, receiver._id, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows a user to like their own private message', async () => {
|
||||||
|
const receiver = await generateUser();
|
||||||
|
|
||||||
|
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
|
||||||
|
message: 'some message :)',
|
||||||
|
toUserId: receiver._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { uniqueMessageId } = sentMessageResult.message;
|
||||||
|
|
||||||
|
const likeResult = await userToSendMessage.post(getLikeUrl(uniqueMessageId));
|
||||||
|
expect(likeResult.likes[userToSendMessage._id]).to.equal(true);
|
||||||
|
|
||||||
|
const messages = await userToSendMessage.get('/inbox/messages');
|
||||||
|
expectMessagesLikeStatus(messages, uniqueMessageId, userToSendMessage._id, true);
|
||||||
|
|
||||||
|
const receiversMessages = await receiver.get('/inbox/messages');
|
||||||
|
|
||||||
|
expectMessagesLikeStatus(receiversMessages, uniqueMessageId, userToSendMessage._id, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unlikes a message', async () => {
|
||||||
|
const receiver = await generateUser();
|
||||||
|
|
||||||
|
const sentMessageResult = await userToSendMessage.post('/members/send-private-message', {
|
||||||
|
message: 'some message :)',
|
||||||
|
toUserId: receiver._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { uniqueMessageId } = sentMessageResult.message;
|
||||||
|
|
||||||
|
const likeResult = await receiver.post(getLikeUrl(uniqueMessageId));
|
||||||
|
|
||||||
|
expect(likeResult.likes[receiver._id]).to.equal(true);
|
||||||
|
|
||||||
|
const unlikeResult = await receiver.post(getLikeUrl(uniqueMessageId));
|
||||||
|
|
||||||
|
expect(unlikeResult.likes[receiver._id]).to.equal(false);
|
||||||
|
|
||||||
|
const messages = await userToSendMessage.get('/inbox/messages');
|
||||||
|
|
||||||
|
const messageToCheck = find(messages, { id: sentMessageResult.message.id });
|
||||||
|
expect(messageToCheck.likes[receiver._id]).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,7 +10,7 @@ describe('events', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns empty array when no events are active', () => {
|
it('returns empty array when no events are active', () => {
|
||||||
clock = sinon.useFakeTimers(new Date('2024-01-08'));
|
clock = sinon.useFakeTimers(new Date('2024-01-11'));
|
||||||
const events = getRepeatingEvents();
|
const events = getRepeatingEvents();
|
||||||
expect(events).to.be.empty;
|
expect(events).to.be.empty;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -144,6 +144,12 @@ describe('Content Schedule', () => {
|
|||||||
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sets the end date in new year for a winter gala', () => {
|
||||||
|
const date = new Date('2025-01-04');
|
||||||
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
|
expect(matchers.seasonalGear.end).to.eql(moment.utc(`2025-03-21T${String(switchoverTime).padStart(2, '0')}:00:00.000Z`).toDate());
|
||||||
|
});
|
||||||
|
|
||||||
it('uses correct date for first hours of the month', () => {
|
it('uses correct date for first hours of the month', () => {
|
||||||
// if the date is checked before CONTENT_SWITCHOVER_TIME_OFFSET,
|
// if the date is checked before CONTENT_SWITCHOVER_TIME_OFFSET,
|
||||||
// it should be considered the previous month
|
// it should be considered the previous month
|
||||||
@@ -184,7 +190,7 @@ describe('Content Schedule', () => {
|
|||||||
const date = new Date('2024-04-15');
|
const date = new Date('2024-04-15');
|
||||||
const matchers = getAllScheduleMatchingGroups(date);
|
const matchers = getAllScheduleMatchingGroups(date);
|
||||||
expect(matchers.premiumHatchingPotions).to.exist;
|
expect(matchers.premiumHatchingPotions).to.exist;
|
||||||
expect(matchers.premiumHatchingPotions.items.length).to.equal(5);
|
expect(matchers.premiumHatchingPotions.items.length).to.equal(6);
|
||||||
expect(matchers.premiumHatchingPotions.items.indexOf('Veggie')).to.not.equal(-1);
|
expect(matchers.premiumHatchingPotions.items.indexOf('Veggie')).to.not.equal(-1);
|
||||||
expect(matchers.premiumHatchingPotions.items.indexOf('Porcelain')).to.not.equal(-1);
|
expect(matchers.premiumHatchingPotions.items.indexOf('Porcelain')).to.not.equal(-1);
|
||||||
});
|
});
|
||||||
@@ -266,6 +272,21 @@ describe('Content Schedule', () => {
|
|||||||
expect(matcher.match('backgroundkey072024')).to.be.true;
|
expect(matcher.match('backgroundkey072024')).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows background matching the month for new backgrounds from multiple years', () => {
|
||||||
|
const date = new Date('2026-07-08');
|
||||||
|
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
||||||
|
expect(matcher.match('backgroundkey072024')).to.be.true;
|
||||||
|
expect(matcher.match('backgroundkey072025')).to.be.true;
|
||||||
|
expect(matcher.match('backgroundkey072026')).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows background matching the previous month in the first week for new backgrounds', () => {
|
||||||
|
const date = new Date('2024-09-02');
|
||||||
|
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
||||||
|
expect(matcher.match('backgroundkey082024')).to.be.true;
|
||||||
|
expect(matcher.match('backgroundkey092024')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('disallows background in the future', () => {
|
it('disallows background in the future', () => {
|
||||||
const date = new Date('2024-07-08');
|
const date = new Date('2024-07-08');
|
||||||
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
||||||
@@ -285,19 +306,26 @@ describe('Content Schedule', () => {
|
|||||||
expect(matcher.match('backgroundkey022021')).to.be.true;
|
expect(matcher.match('backgroundkey022021')).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows background even yeared backgrounds in first half of year', () => {
|
it('allows even yeared backgrounds in first half of year', () => {
|
||||||
const date = new Date('2025-02-08');
|
const date = new Date('2025-02-08');
|
||||||
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
||||||
expect(matcher.match('backgroundkey022024')).to.be.true;
|
expect(matcher.match('backgroundkey022024')).to.be.true;
|
||||||
expect(matcher.match('backgroundkey082022')).to.be.true;
|
expect(matcher.match('backgroundkey082022')).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows background odd yeared backgrounds in second half of year', () => {
|
it('allows odd yeared backgrounds in second half of year', () => {
|
||||||
const date = new Date('2024-08-08');
|
const date = new Date('2024-08-08');
|
||||||
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
||||||
expect(matcher.match('backgroundkey022023')).to.be.true;
|
expect(matcher.match('backgroundkey022023')).to.be.true;
|
||||||
expect(matcher.match('backgroundkey082021')).to.be.true;
|
expect(matcher.match('backgroundkey082021')).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows odd yeared backgrounds in beginning of january', () => {
|
||||||
|
const date = new Date('2025-01-06');
|
||||||
|
const matcher = getAllScheduleMatchingGroups(date).backgrounds;
|
||||||
|
expect(matcher.match('backgroundkey122024'), 'backgroundkey122024').to.be.true;
|
||||||
|
expect(matcher.match('backgroundkey062023'), 'backgroundkey062022').to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('timeTravelers matcher', () => {
|
describe('timeTravelers matcher', () => {
|
||||||
|
|||||||
@@ -74,15 +74,10 @@ export async function getDocument (collectionName, doc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
before(done => {
|
before(done => {
|
||||||
mongoose.connection.on('open', err => {
|
mongoose.connection.once('open', async err => {
|
||||||
if (err) return done(err);
|
if (err) throw err;
|
||||||
return resetHabiticaDB()
|
await resetHabiticaDB();
|
||||||
.then(() => {
|
done();
|
||||||
done();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ module.exports = {
|
|||||||
extends: [
|
extends: [
|
||||||
'habitrpg/lib/vue',
|
'habitrpg/lib/vue',
|
||||||
],
|
],
|
||||||
ignorePatterns: ['dist/', 'node_modules/'],
|
ignorePatterns: ['dist/', 'node_modules/', '*.d.ts'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
|||||||
286
website/client/package-lock.json
generated
286
website/client/package-lock.json
generated
@@ -3934,9 +3934,9 @@
|
|||||||
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
|
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
|
||||||
},
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.2",
|
"version": "1.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||||
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "3.1.2",
|
||||||
"content-type": "~1.0.5",
|
"content-type": "~1.0.5",
|
||||||
@@ -3946,7 +3946,7 @@
|
|||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"iconv-lite": "0.4.24",
|
"iconv-lite": "0.4.24",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"qs": "6.11.0",
|
"qs": "6.13.0",
|
||||||
"raw-body": "2.5.2",
|
"raw-body": "2.5.2",
|
||||||
"type-is": "~1.6.18",
|
"type-is": "~1.6.18",
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
@@ -4140,6 +4140,33 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bound": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"get-intrinsic": "^1.2.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
@@ -4616,9 +4643,9 @@
|
|||||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
"version": "0.6.0",
|
"version": "0.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -5422,6 +5449,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
||||||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
|
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.0",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/duplexer": {
|
"node_modules/duplexer": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||||
@@ -5464,9 +5504,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
"version": "1.0.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@@ -5588,12 +5628,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dependencies": {
|
|
||||||
"get-intrinsic": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@@ -5611,6 +5648,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
|
||||||
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
|
"integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-set-tostringtag": {
|
"node_modules/es-set-tostringtag": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
|
||||||
@@ -6695,36 +6743,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.19.2",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
"body-parser": "1.20.2",
|
"body-parser": "1.20.3",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"content-type": "~1.0.4",
|
"content-type": "~1.0.4",
|
||||||
"cookie": "0.6.0",
|
"cookie": "0.7.1",
|
||||||
"cookie-signature": "1.0.6",
|
"cookie-signature": "1.0.6",
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"etag": "~1.8.1",
|
"etag": "~1.8.1",
|
||||||
"finalhandler": "1.2.0",
|
"finalhandler": "1.3.1",
|
||||||
"fresh": "0.5.2",
|
"fresh": "0.5.2",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"merge-descriptors": "1.0.1",
|
"merge-descriptors": "1.0.3",
|
||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.7",
|
"path-to-regexp": "0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.11.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
"safe-buffer": "5.2.1",
|
"safe-buffer": "5.2.1",
|
||||||
"send": "0.18.0",
|
"send": "0.19.0",
|
||||||
"serve-static": "1.15.0",
|
"serve-static": "1.16.2",
|
||||||
"setprototypeof": "1.2.0",
|
"setprototypeof": "1.2.0",
|
||||||
"statuses": "2.0.1",
|
"statuses": "2.0.1",
|
||||||
"type-is": "~1.6.18",
|
"type-is": "~1.6.18",
|
||||||
@@ -6733,6 +6781,10 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express/node_modules/array-flatten": {
|
"node_modules/express/node_modules/array-flatten": {
|
||||||
@@ -6877,12 +6929,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
@@ -7125,15 +7177,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
|
||||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"dunder-proto": "^1.0.0",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.0.0",
|
||||||
"function-bind": "^1.1.2",
|
"function-bind": "^1.1.2",
|
||||||
"has-proto": "^1.0.1",
|
"gopd": "^1.2.0",
|
||||||
"has-symbols": "^1.0.3",
|
"has-symbols": "^1.1.0",
|
||||||
"hasown": "^2.0.0"
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -7253,11 +7310,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dependencies": {
|
"engines": {
|
||||||
"get-intrinsic": "^1.1.3"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -7353,9 +7410,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has-symbols": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
@@ -7383,9 +7440,9 @@
|
|||||||
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
|
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
},
|
},
|
||||||
@@ -8919,6 +8976,14 @@
|
|||||||
"markdown-it": "bin/markdown-it.js"
|
"markdown-it": "bin/markdown-it.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mdn-data": {
|
"node_modules/mdn-data": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||||
@@ -8958,9 +9023,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/merge-source-map": {
|
"node_modules/merge-source-map": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@@ -9862,9 +9930,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.13.1",
|
"version": "1.13.3",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
|
||||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -10310,9 +10381,9 @@
|
|||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
|
||||||
},
|
},
|
||||||
"node_modules/path-type": {
|
"node_modules/path-type": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@@ -11129,11 +11200,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.11.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.0.4"
|
"side-channel": "^1.0.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
@@ -11736,9 +11807,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.18.0",
|
"version": "0.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
@@ -11771,6 +11842,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/send/node_modules/encodeurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/send/node_modules/ms": {
|
"node_modules/send/node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -11855,14 +11934,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
"version": "1.15.0",
|
"version": "1.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"encodeurl": "~1.0.2",
|
"encodeurl": "~2.0.0",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"send": "0.18.0"
|
"send": "0.19.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
@@ -11951,13 +12030,68 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.4",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.0",
|
"es-errors": "^1.3.0",
|
||||||
"get-intrinsic": "^1.0.2",
|
"object-inspect": "^1.13.3",
|
||||||
"object-inspect": "^1.9.0"
|
"side-channel-list": "^1.0.0",
|
||||||
|
"side-channel-map": "^1.0.1",
|
||||||
|
"side-channel-weakmap": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-list": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-map": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-weakmap": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3",
|
||||||
|
"side-channel-map": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
height: 219px;
|
height: 219px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Pet_HatchingPotion_Dessert, .Pet_HatchingPotion_Veggie, .Pet_HatchingPotion_Windup, .Pet_HatchingPotion_VirtualPet, .Pet_HatchingPotion_Fungi {
|
.Pet_HatchingPotion_Dessert, .Pet_HatchingPotion_Veggie, .Pet_HatchingPotion_Windup,
|
||||||
|
.Pet_HatchingPotion_VirtualPet, .Pet_HatchingPotion_Fungi, .Pet_HatchingPotion_Cryptid {
|
||||||
width: 68px;
|
width: 68px;
|
||||||
height: 68px;
|
height: 68px;
|
||||||
}
|
}
|
||||||
@@ -47,6 +48,10 @@
|
|||||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Fungi.gif") no-repeat;
|
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Fungi.gif") no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Pet_HatchingPotion_Cryptid {
|
||||||
|
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Cryptid.gif") no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
.Gems {
|
.Gems {
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
margin-right:5px;
|
margin-right:5px;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@
|
|||||||
top: -16px !important;
|
top: -16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
$foolPets: Veggie, Dessert, VirtualPet, TeaShop, Fungi;
|
$foolPets: Veggie, Dessert, VirtualPet, TeaShop, Fungi, Cryptid;
|
||||||
|
|
||||||
@each $foolPet in $foolPets {
|
@each $foolPet in $foolPets {
|
||||||
.Pet.Pet-FlyingPig-#{$foolPet} {
|
.Pet.Pet-FlyingPig-#{$foolPet} {
|
||||||
|
|||||||
@@ -101,8 +101,7 @@
|
|||||||
|
|
||||||
.btn-secondary,
|
.btn-secondary,
|
||||||
.dropdown > .btn-secondary.dropdown-toggle:not(.btn-success),
|
.dropdown > .btn-secondary.dropdown-toggle:not(.btn-success),
|
||||||
.show > .btn-secondary.dropdown-toggle:not(.btn-success)
|
.show > .btn-secondary.dropdown-toggle:not(.btn-success) {
|
||||||
{
|
|
||||||
background: $white;
|
background: $white;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
color: $gray-50;
|
color: $gray-50;
|
||||||
@@ -298,6 +297,16 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-flat,
|
||||||
|
.dropdown > .btn-flat.dropdown-toggle:not(.btn-success),
|
||||||
|
.show > .btn-flat.dropdown-toggle:not(.btn-success) {
|
||||||
|
&.with-icon {
|
||||||
|
.svg-icon.color {
|
||||||
|
color: var(--icon-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.btn-cancel {
|
.btn-cancel {
|
||||||
color: $blue-10;
|
color: $blue-10;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,12 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
|
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-min-width {
|
||||||
|
.dropdown-menu {
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// shared dropdown-item styles
|
// shared dropdown-item styles
|
||||||
@@ -54,6 +59,8 @@
|
|||||||
color: $gray-50 !important;
|
color: $gray-50 !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
--dropdown-item-hover-icon-color: #{$gray-200};
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
@@ -88,7 +95,7 @@
|
|||||||
|
|
||||||
&:not(:hover) {
|
&:not(:hover) {
|
||||||
.with-icon .svg-icon {
|
.with-icon .svg-icon {
|
||||||
color: $gray-200;
|
color: var(dropdown-item-hover-icon-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +158,7 @@
|
|||||||
|
|
||||||
// selectList.vue items sizing
|
// selectList.vue items sizing
|
||||||
.selectListItem .dropdown-item {
|
.selectListItem .dropdown-item {
|
||||||
padding: 0.25rem 0.75rem;
|
padding: 0.25rem 1rem 0.25rem 0.75rem;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
||||||
&:active, &:hover, &:focus, &.active {
|
&:active, &:hover, &:focus, &.active {
|
||||||
|
|||||||
3
website/client/src/assets/svg/bluesky.svg
Normal file
3
website/client/src/assets/svg/bluesky.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4,0C1.79,0,0,1.79,0,4v16c0,2.21,1.79,4,4,4h16c2.21,0,4-1.79,4-4V4c0-2.21-1.79-4-4-4H4ZM12,11.57c-.72-1.49-2.7-4.26-4.53-5.63-1.32-.99-3.47-1.75-3.47.68,0,.49.28,4.08.44,4.66.57,2.03,2.65,2.55,4.5,2.23-3.24.55-4.06,2.36-2.28,4.17,3.38,3.44,4.85-.86,5.23-1.97h0s0,0,0,0c.07-.2.1-.29.1-.21,0-.08.03.01.1.22h0c.38,1.1,1.85,5.41,5.23,1.97,1.78-1.81.95-3.63-2.28-4.17,1.85.31,3.93-.2,4.5-2.23.16-.58.44-4.18.44-4.66,0-2.43-2.14-1.67-3.47-.68-1.83,1.37-3.81,4.14-4.53,5.63Z" fill-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 572 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
||||||
<path d="M20,0H4A4,4,0,0,0,0,4V20a4,4,0,0,0,4,4H20a4,4,0,0,0,4-4V4A4,4,0,0,0,20,0ZM18.36,8.74c0,.14,0,.29,0,.43A9.34,9.34,0,0,1,4,17a6.85,6.85,0,0,0,.79,0,6.57,6.57,0,0,0,4.07-1.4A3.29,3.29,0,0,1,5.8,13.39a4.1,4.1,0,0,0,.62,0,3.49,3.49,0,0,0,.86-.11,3.28,3.28,0,0,1-2.63-3.22v0a3.35,3.35,0,0,0,1.48.42A3.29,3.29,0,0,1,4.67,7.76,3.22,3.22,0,0,1,5.12,6.1a9.3,9.3,0,0,0,6.76,3.43,3.67,3.67,0,0,1-.08-.75,3.28,3.28,0,0,1,5.67-2.24,6.54,6.54,0,0,0,2.08-.79,3.22,3.22,0,0,1-1.44,1.8A6.67,6.67,0,0,0,20,7.05,7.31,7.31,0,0,1,18.36,8.74Z" fill-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 622 B |
@@ -25,9 +25,9 @@
|
|||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
Homepage
|
Homepage
|
||||||
</router-link>or
|
</router-link>or
|
||||||
<router-link :to="contactUsLink">
|
<a href="mailto:admin@habitica.com">
|
||||||
Contact Us
|
Contact Us
|
||||||
</router-link>about the issue.
|
</a>about the issue.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,12 +40,6 @@ import { mapState } from '@/libs/store';
|
|||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['isUserLoggedIn']),
|
...mapState(['isUserLoggedIn']),
|
||||||
contactUsLink () {
|
|
||||||
if (this.isUserLoggedIn) {
|
|
||||||
return { name: 'guild', params: { groupId: 'a29da26b-37de-4a71-b0c6-48e72a900dac' } };
|
|
||||||
}
|
|
||||||
return { name: 'contact' };
|
|
||||||
},
|
|
||||||
retiredChatPage () {
|
retiredChatPage () {
|
||||||
return this.$route.fullPath.indexOf('/groups') !== -1;
|
return this.$route.fullPath.indexOf('/groups') !== -1;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 offset-3">
|
<div class="col-6 offset-3">
|
||||||
<div class="shop_armoire"></div>
|
<Sprite image-name="shop_armoire" />
|
||||||
<p>{{ $t('armoireLastItem') }}</p>
|
<p>{{ $t('armoireLastItem') }}</p>
|
||||||
<p>{{ $t('armoireNotesEmpty') }}</p>
|
<p>{{ $t('armoireNotesEmpty') }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +34,12 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Sprite,
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close () {
|
close () {
|
||||||
this.$root.$emit('bv::hide::modal', 'armoire-empty');
|
this.$root.$emit('bv::hide::modal', 'armoire-empty');
|
||||||
|
|||||||
@@ -95,7 +95,11 @@
|
|||||||
@click="clickDisableClasses(); close();"
|
@click="clickDisableClasses(); close();"
|
||||||
>{{ $t('optOutOfClasses') }}</span>
|
>{{ $t('optOutOfClasses') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="opt-out-description">{{ $t('optOutOfClassesText') }}</span>
|
<div
|
||||||
|
v-once
|
||||||
|
class="opt-out-description"
|
||||||
|
v-html="$t('optOutOfClassesText')"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="questClass"></div>
|
<Sprite :image-name="questClass" />
|
||||||
</section>
|
</section>
|
||||||
<!-- @TODO: Keep this? .checkboxinput(type='checkbox', v-model=
|
<!-- @TODO: Keep this? .checkboxinput(type='checkbox', v-model=
|
||||||
'user.preferences.suppressModals.levelUp', @change='changeLevelupSuppress()')
|
'user.preferences.suppressModals.levelUp', @change='changeLevelupSuppress()')
|
||||||
@@ -150,15 +150,12 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
|
|||||||
section.greyed {
|
section.greyed {
|
||||||
padding-bottom: 17px
|
padding-bottom: 17px
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll {
|
|
||||||
margin: -11px auto 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Avatar from '../avatar';
|
import Avatar from '../avatar';
|
||||||
|
import Sprite from '@/components/ui/sprite';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import starGroup from '@/assets/svg/star-group.svg';
|
import starGroup from '@/assets/svg/star-group.svg';
|
||||||
import sparkles from '@/assets/svg/sparkles-left.svg';
|
import sparkles from '@/assets/svg/sparkles-left.svg';
|
||||||
@@ -173,6 +170,7 @@ const levelQuests = {
|
|||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -191,7 +189,9 @@ export default {
|
|||||||
return this.user.stats.lvl in levelQuests;
|
return this.user.stats.lvl in levelQuests;
|
||||||
},
|
},
|
||||||
questClass () {
|
questClass () {
|
||||||
return `scroll inventory_quest_scroll_${levelQuests[this.user.stats.lvl]}`;
|
const questKey = levelQuests[this.user.stats.lvl];
|
||||||
|
if (questKey) return `inventory_quest_scroll_${questKey}`;
|
||||||
|
return '';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
<p v-html="$t('moreGearAchievements')"></p>
|
<p v-html="$t('moreGearAchievements')"></p>
|
||||||
<br>
|
<br>
|
||||||
</div>
|
</div>
|
||||||
<div class="shop_armoire"></div>
|
<Sprite image-name="shop_armoire" />
|
||||||
<p v-html="$t('armoireUnlocked')"></p>
|
<p v-html="$t('armoireUnlocked')"></p>
|
||||||
<br>
|
<br>
|
||||||
<button
|
<button
|
||||||
@@ -87,11 +87,13 @@
|
|||||||
import achievementFooter from './achievementFooter';
|
import achievementFooter from './achievementFooter';
|
||||||
import achievementAvatar from './achievementAvatar';
|
import achievementAvatar from './achievementAvatar';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
|
import Sprite from '@/components/ui/sprite.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
achievementFooter,
|
achievementFooter,
|
||||||
achievementAvatar,
|
achievementAvatar,
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({ user: 'user.data' }),
|
...mapState({ user: 'user.data' }),
|
||||||
|
|||||||
@@ -92,8 +92,6 @@ export default {
|
|||||||
params: { userIdentifier },
|
params: { userIdentifier },
|
||||||
}).catch(failure => {
|
}).catch(failure => {
|
||||||
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
||||||
// the admin has requested that the same user be displayed again so reload the page
|
|
||||||
// (e.g., if they changed their mind about changes they were making)
|
|
||||||
this.$router.go();
|
this.$router.go();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -101,14 +99,16 @@ export default {
|
|||||||
|
|
||||||
async loadUser (userIdentifier) {
|
async loadUser (userIdentifier) {
|
||||||
const id = userIdentifier || this.user._id;
|
const id = userIdentifier || this.user._id;
|
||||||
|
if (this.$router.currentRoute.name === 'adminPanelUser') {
|
||||||
this.$router.push({
|
await this.$router.push({
|
||||||
|
name: 'adminPanel',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await this.$router.push({
|
||||||
name: 'adminPanelUser',
|
name: 'adminPanelUser',
|
||||||
params: { userIdentifier: id },
|
params: { userIdentifier: id },
|
||||||
}).catch(failure => {
|
}).catch(failure => {
|
||||||
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
||||||
// the admin has requested that the same user be displayed again so reload the page
|
|
||||||
// (e.g., if they changed their mind about changes they were making)
|
|
||||||
this.$router.go();
|
this.$router.go();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
|
import VueRouter from 'vue-router';
|
||||||
|
|
||||||
|
const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
async saveHero ({ hero, msg = 'User', clearData }) {
|
async saveHero ({
|
||||||
|
hero,
|
||||||
|
msg = 'User',
|
||||||
|
clearData,
|
||||||
|
reloadData,
|
||||||
|
}) {
|
||||||
await this.$store.dispatch('hall:updateHero', { heroDetails: hero });
|
await this.$store.dispatch('hall:updateHero', { heroDetails: hero });
|
||||||
await this.$store.dispatch('snackbars:add', {
|
await this.$store.dispatch('snackbars:add', {
|
||||||
title: '',
|
title: '',
|
||||||
@@ -14,6 +23,20 @@ export default {
|
|||||||
// The admin should re-fetch the data if they need to keep working on that user.
|
// The admin should re-fetch the data if they need to keep working on that user.
|
||||||
this.$emit('clear-data');
|
this.$emit('clear-data');
|
||||||
this.$router.push({ name: 'adminPanel' });
|
this.$router.push({ name: 'adminPanel' });
|
||||||
|
} else if (reloadData) {
|
||||||
|
if (this.$router.currentRoute.name === 'adminPanelUser') {
|
||||||
|
await this.$router.push({
|
||||||
|
name: 'adminPanel',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await this.$router.push({
|
||||||
|
name: 'adminPanelUser',
|
||||||
|
params: { userIdentifier: hero._id },
|
||||||
|
}).catch(failure => {
|
||||||
|
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
||||||
|
this.$router.go();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,11 @@
|
|||||||
>
|
>
|
||||||
Could not find any matching users.
|
Could not find any matching users.
|
||||||
</div>
|
</div>
|
||||||
<loading-spinner class="mx-auto mb-2" dark-color="true" v-if="isSearching" />
|
<loading-spinner
|
||||||
|
v-if="isSearching"
|
||||||
|
class="mx-auto mb-2"
|
||||||
|
dark-color="true"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="users.length > 0"
|
v-if="users.length > 0"
|
||||||
class="list-group"
|
class="list-group"
|
||||||
@@ -59,6 +63,10 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
},
|
},
|
||||||
|
beforeRouteUpdate (to, from, next) {
|
||||||
|
this.userIdentifier = to.params.userIdentifier;
|
||||||
|
next();
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
userIdentifier: '',
|
userIdentifier: '',
|
||||||
@@ -70,10 +78,6 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState({ user: 'user.data' }),
|
...mapState({ user: 'user.data' }),
|
||||||
},
|
},
|
||||||
beforeRouteUpdate (to, from, next) {
|
|
||||||
this.userIdentifier = to.params.userIdentifier;
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
watch: {
|
watch: {
|
||||||
userIdentifier () {
|
userIdentifier () {
|
||||||
this.isSearching = true;
|
this.isSearching = true;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<li
|
<li
|
||||||
v-for="item in achievements"
|
v-for="item in achievements"
|
||||||
:key="item.path"
|
:key="item.path"
|
||||||
|
v-b-tooltip.hover="item.notes"
|
||||||
>
|
>
|
||||||
<form @submit.prevent="saveItem(item)">
|
<form @submit.prevent="saveItem(item)">
|
||||||
<span
|
<span
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
{{ item.value }}
|
{{ item.value }}
|
||||||
</span>
|
</span>
|
||||||
:
|
:
|
||||||
{{ item.text || item.key }}
|
{{ item.text || item.key }} - <i> {{ item.key }} </i>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -68,6 +69,7 @@
|
|||||||
<li
|
<li
|
||||||
v-for="item in nestedAchievements[achievementType]"
|
v-for="item in nestedAchievements[achievementType]"
|
||||||
:key="item.path"
|
:key="item.path"
|
||||||
|
v-b-tooltip.hover="item.notes"
|
||||||
>
|
>
|
||||||
<form @submit.prevent="saveItem(item)">
|
<form @submit.prevent="saveItem(item)">
|
||||||
<span
|
<span
|
||||||
@@ -78,7 +80,7 @@
|
|||||||
{{ item.value }}
|
{{ item.value }}
|
||||||
</span>
|
</span>
|
||||||
:
|
:
|
||||||
{{ item.text || item.key }}
|
{{ item.text || item.key }} - <i> {{ item.key }} </i>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -143,79 +145,28 @@ function getText (achievementItem) {
|
|||||||
}
|
}
|
||||||
const { titleKey } = achievementItem;
|
const { titleKey } = achievementItem;
|
||||||
if (titleKey !== undefined) {
|
if (titleKey !== undefined) {
|
||||||
return i18n.t(titleKey, 'en');
|
return i18n.t(titleKey);
|
||||||
}
|
}
|
||||||
const { singularTitleKey } = achievementItem;
|
const { singularTitleKey } = achievementItem;
|
||||||
if (singularTitleKey !== undefined) {
|
if (singularTitleKey !== undefined) {
|
||||||
return i18n.t(singularTitleKey, 'en');
|
return i18n.t(singularTitleKey);
|
||||||
}
|
}
|
||||||
return achievementItem.key;
|
return achievementItem.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function collateItemData (self) {
|
function getNotes (achievementItem, count) {
|
||||||
const achievements = [];
|
if (achievementItem === undefined) {
|
||||||
const nestedAchievements = {};
|
return '';
|
||||||
const basePath = 'achievements';
|
|
||||||
const ownedAchievements = self.hero.achievements;
|
|
||||||
const allAchievements = content.achievements;
|
|
||||||
|
|
||||||
for (const key of Object.keys(ownedAchievements)) {
|
|
||||||
const value = ownedAchievements[key];
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
nestedAchievements[key] = [];
|
|
||||||
for (const nestedKey of Object.keys(value)) {
|
|
||||||
const valueIsInteger = self.integerTypes.includes(key);
|
|
||||||
let text = nestedKey;
|
|
||||||
if (allAchievements[key] && allAchievements[key][nestedKey]) {
|
|
||||||
text = getText(allAchievements[key][nestedKey]);
|
|
||||||
}
|
|
||||||
nestedAchievements[key].push({
|
|
||||||
key: nestedKey,
|
|
||||||
text,
|
|
||||||
achievementType: key,
|
|
||||||
modified: false,
|
|
||||||
path: `${basePath}.${key}.${nestedKey}`,
|
|
||||||
value: value[nestedKey],
|
|
||||||
valueIsInteger,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const valueIsInteger = self.integerTypes.includes(key);
|
|
||||||
achievements.push({
|
|
||||||
key,
|
|
||||||
text: getText(allAchievements[key]),
|
|
||||||
modified: false,
|
|
||||||
path: `${basePath}.${key}`,
|
|
||||||
value: ownedAchievements[key],
|
|
||||||
valueIsInteger,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const { textKey } = achievementItem;
|
||||||
for (const key of Object.keys(allAchievements)) {
|
if (textKey !== undefined) {
|
||||||
if (key !== '' && !key.endsWith('UltimateGear') && !key.endsWith('Quest')) {
|
return i18n.t(textKey, { count });
|
||||||
if (ownedAchievements[key] === undefined) {
|
|
||||||
const valueIsInteger = self.integerTypes.includes(key);
|
|
||||||
achievements.push({
|
|
||||||
key,
|
|
||||||
text: getText(allAchievements[key]),
|
|
||||||
modified: false,
|
|
||||||
path: `${basePath}.${key}`,
|
|
||||||
value: valueIsInteger ? 0 : false,
|
|
||||||
valueIsInteger,
|
|
||||||
neverOwned: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const { singularTextKey } = achievementItem;
|
||||||
self.achievements = achievements;
|
if (singularTextKey !== undefined) {
|
||||||
self.nestedAchievements = nestedAchievements;
|
return i18n.t(singularTextKey, { count });
|
||||||
}
|
}
|
||||||
|
return '';
|
||||||
function resetData (self) {
|
|
||||||
collateItemData(self);
|
|
||||||
self.nestedAchievementKeys.forEach(itemType => { self.expandItemType[itemType] = false; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -241,26 +192,34 @@ export default {
|
|||||||
},
|
},
|
||||||
nestedAchievementKeys: ['quests', 'ultimateGearSets'],
|
nestedAchievementKeys: ['quests', 'ultimateGearSets'],
|
||||||
integerTypes: ['streak', 'perfect', 'birthday', 'habiticaDays', 'habitSurveys', 'habitBirthdays',
|
integerTypes: ['streak', 'perfect', 'birthday', 'habiticaDays', 'habitSurveys', 'habitBirthdays',
|
||||||
'valentine', 'congrats', 'shinySeed', 'goodluck', 'thankyou', 'seafoam', 'snowball', 'quests'],
|
'valentine', 'congrats', 'shinySeed', 'goodluck', 'thankyou', 'seafoam', 'snowball', 'quests',
|
||||||
|
'rebirths', 'rebirthLevel', 'greeting', 'spookySparkles', 'nye', 'costumeContests', 'congrats',
|
||||||
|
'getwell', 'beastMasterCount', 'mountMasterCount', 'triadBingoCount',
|
||||||
|
],
|
||||||
|
cardTypes: ['greeting', 'birthday', 'valentine', 'goodluck', 'thankyou', 'greeting', 'nye',
|
||||||
|
'congrats', 'getwell'],
|
||||||
achievements: [],
|
achievements: [],
|
||||||
nestedAchievements: {},
|
nestedAchievements: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
resetCounter () {
|
resetCounter () {
|
||||||
resetData(this);
|
this.resetData();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
resetData(this);
|
this.resetData();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async saveItem (item) {
|
async saveItem (item) {
|
||||||
// prepare the item's new value and path for being saved
|
await this.saveHero({
|
||||||
this.hero.achievementPath = item.path;
|
hero: {
|
||||||
this.hero.achievementVal = item.value;
|
_id: this.hero._id,
|
||||||
|
achievementPath: item.path,
|
||||||
await this.saveHero({ hero: this.hero, msg: item.path });
|
achievementVal: item.value,
|
||||||
|
},
|
||||||
|
msg: item.path,
|
||||||
|
});
|
||||||
item.modified = false;
|
item.modified = false;
|
||||||
},
|
},
|
||||||
enableValueChange (item) {
|
enableValueChange (item) {
|
||||||
@@ -270,6 +229,85 @@ export default {
|
|||||||
item.value = !item.value;
|
item.value = !item.value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
resetData () {
|
||||||
|
this.collateItemData();
|
||||||
|
this.nestedAchievementKeys.forEach(itemType => { this.expandItemType[itemType] = false; });
|
||||||
|
},
|
||||||
|
collateItemData () {
|
||||||
|
const achievements = [];
|
||||||
|
const nestedAchievements = {};
|
||||||
|
const basePath = 'achievements';
|
||||||
|
const ownedAchievements = this.hero.achievements;
|
||||||
|
const allAchievements = content.achievements;
|
||||||
|
|
||||||
|
const ownedKeys = Object.keys(ownedAchievements).sort();
|
||||||
|
for (const key of ownedKeys) {
|
||||||
|
const value = ownedAchievements[key];
|
||||||
|
let contentKey = key;
|
||||||
|
if (this.cardTypes.indexOf(key) !== -1) {
|
||||||
|
contentKey += 'Cards';
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
nestedAchievements[key] = [];
|
||||||
|
for (const nestedKey of Object.keys(value)) {
|
||||||
|
const valueIsInteger = this.integerTypes.includes(key);
|
||||||
|
let text = nestedKey;
|
||||||
|
if (allAchievements[key] && allAchievements[key][contentKey]) {
|
||||||
|
text = getText(allAchievements[key][contentKey]);
|
||||||
|
}
|
||||||
|
let notes = '';
|
||||||
|
if (allAchievements[key] && allAchievements[key][contentKey]) {
|
||||||
|
notes = getNotes(allAchievements[key][contentKey], ownedAchievements[key]);
|
||||||
|
}
|
||||||
|
nestedAchievements[key].push({
|
||||||
|
key: nestedKey,
|
||||||
|
text,
|
||||||
|
notes,
|
||||||
|
achievementType: key,
|
||||||
|
modified: false,
|
||||||
|
path: `${basePath}.${key}.${nestedKey}`,
|
||||||
|
value: value[nestedKey],
|
||||||
|
valueIsInteger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const valueIsInteger = this.integerTypes.includes(key);
|
||||||
|
achievements.push({
|
||||||
|
key,
|
||||||
|
text: getText(allAchievements[contentKey]),
|
||||||
|
notes: getNotes(allAchievements[contentKey], ownedAchievements[key]),
|
||||||
|
modified: false,
|
||||||
|
path: `${basePath}.${key}`,
|
||||||
|
value: ownedAchievements[key],
|
||||||
|
valueIsInteger,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allKeys = Object.keys(allAchievements).sort();
|
||||||
|
|
||||||
|
for (const key of allKeys) {
|
||||||
|
if (key !== '' && !key.endsWith('UltimateGear') && !key.endsWith('Quest')) {
|
||||||
|
const ownedKey = key.replace('Cards', '');
|
||||||
|
if (ownedAchievements[ownedKey] === undefined) {
|
||||||
|
const valueIsInteger = this.integerTypes.includes(ownedKey);
|
||||||
|
achievements.push({
|
||||||
|
key: ownedKey,
|
||||||
|
text: getText(allAchievements[key]),
|
||||||
|
notes: getNotes(allAchievements[key], 0),
|
||||||
|
modified: false,
|
||||||
|
path: `${basePath}.${ownedKey}`,
|
||||||
|
value: valueIsInteger ? 0 : false,
|
||||||
|
valueIsInteger,
|
||||||
|
neverOwned: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.achievements = achievements;
|
||||||
|
this.nestedAchievements = nestedAchievements;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="saveHero({ hero, msg: 'Contributor details', clearData: true })">
|
<form
|
||||||
|
@submit.prevent="saveHero({ hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
contributor: hero.contributor,
|
||||||
|
secret: hero.secret,
|
||||||
|
permissions: hero.permissions,
|
||||||
|
}, msg: 'Contributor details', clearData: true })"
|
||||||
|
>
|
||||||
<div class="card mt-2">
|
<div class="card mt-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
@@ -8,6 +15,12 @@
|
|||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Contributor Details
|
Contributor Details
|
||||||
|
<b
|
||||||
|
v-if="hasUnsavedChanges && !expand"
|
||||||
|
class="text-warning float-right"
|
||||||
|
>
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -104,13 +117,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="expand"
|
v-if="expand"
|
||||||
class="card-footer"
|
class="card-footer d-flex align-items-center justify-content-between"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Save"
|
value="Save"
|
||||||
class="btn btn-primary mt-1"
|
class="btn btn-primary mt-1"
|
||||||
>
|
>
|
||||||
|
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -190,6 +206,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
hasUnsavedChanges: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="saveHero({ hero, msg: 'Authentication' })">
|
<form
|
||||||
|
@submit.prevent="saveHero({ hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
auth: hero.auth,
|
||||||
|
preferences: hero.preferences,
|
||||||
|
}, msg: 'Authentication' })"
|
||||||
|
>
|
||||||
<div class="card mt-2">
|
<div class="card mt-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
@@ -38,7 +44,10 @@
|
|||||||
<strong v-else>No</strong>
|
<strong v-else>No</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="cronError" class="form-group row">
|
<div
|
||||||
|
v-if="cronError"
|
||||||
|
class="form-group row"
|
||||||
|
>
|
||||||
<label class="col-sm-3 col-form-label">lastCron value:</label>
|
<label class="col-sm-3 col-form-label">lastCron value:</label>
|
||||||
<strong>{{ hero.lastCron | formatDate }}</strong>
|
<strong>{{ hero.lastCron | formatDate }}</strong>
|
||||||
<br>
|
<br>
|
||||||
@@ -53,12 +62,12 @@
|
|||||||
<div class="col-sm-9 col-form-label">
|
<div class="col-sm-9 col-form-label">
|
||||||
<strong>
|
<strong>
|
||||||
{{ hero.auth.timestamps.loggedin | formatDate }}</strong>
|
{{ hero.auth.timestamps.loggedin | formatDate }}</strong>
|
||||||
<button
|
<a
|
||||||
class="btn btn-warning btn-sm ml-4"
|
class="btn btn-warning btn-sm ml-4"
|
||||||
@click="resetCron()"
|
@click="resetCron()"
|
||||||
>
|
>
|
||||||
Reset Cron to Yesterday
|
Reset Cron to Yesterday
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
@@ -110,13 +119,14 @@
|
|||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-3 col-form-label">API Token</label>
|
<label class="col-sm-3 col-form-label">API Token</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<button
|
<a
|
||||||
|
href="#"
|
||||||
value="Change API Token"
|
value="Change API Token"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
@click="changeApiToken()"
|
@click="changeApiToken()"
|
||||||
>
|
>
|
||||||
Change API Token
|
Change API Token
|
||||||
</button>
|
</a>
|
||||||
<div
|
<div
|
||||||
v-if="tokenModified"
|
v-if="tokenModified"
|
||||||
>
|
>
|
||||||
@@ -268,13 +278,24 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
async changeApiToken () {
|
async changeApiToken () {
|
||||||
this.hero.changeApiToken = true;
|
await this.saveHero({
|
||||||
await this.saveHero({ hero: this.hero, msg: 'API Token' });
|
hero: {
|
||||||
|
_id: this.hero._id,
|
||||||
|
changeApiToken: true,
|
||||||
|
},
|
||||||
|
msg: 'API Token',
|
||||||
|
});
|
||||||
this.tokenModified = true;
|
this.tokenModified = true;
|
||||||
},
|
},
|
||||||
resetCron () {
|
resetCron () {
|
||||||
this.hero.resetCron = true;
|
this.saveHero({
|
||||||
this.saveHero({ hero: this.hero, msg: 'Last Cron', clearData: true });
|
hero: {
|
||||||
|
_id: this.hero._id,
|
||||||
|
resetCron: true,
|
||||||
|
},
|
||||||
|
msg: 'Last Cron',
|
||||||
|
clearData: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
:
|
:
|
||||||
<span :class="{ ownedItem: !item.neverOwned }">{{ item.text }}</span>
|
<span :class="{ ownedItem: !item.neverOwned }">{{ item.text }}</span>
|
||||||
</span>
|
</span>
|
||||||
{{ item.set }}
|
- {{ itemType }}.{{item.key}} - <i> {{ item.set }}</i>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="item.modified"
|
v-if="item.modified"
|
||||||
@@ -232,11 +232,14 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async saveItem (item) {
|
async saveItem (item) {
|
||||||
// prepare the item's new value and path for being saved
|
await this.saveHero({
|
||||||
this.hero.purchasedPath = item.path;
|
hero: {
|
||||||
this.hero.purchasedVal = item.value;
|
_id: this.hero._id,
|
||||||
|
purchasedPath: item.path,
|
||||||
await this.saveHero({ hero: this.hero, msg: item.path });
|
purchasedVal: item.value,
|
||||||
|
},
|
||||||
|
msg: item.path,
|
||||||
|
});
|
||||||
item.modified = false;
|
item.modified = false;
|
||||||
},
|
},
|
||||||
enableValueChange (item) {
|
enableValueChange (item) {
|
||||||
|
|||||||
@@ -15,10 +15,17 @@
|
|||||||
<privileges-and-gems
|
<privileges-and-gems
|
||||||
:hero="hero"
|
:hero="hero"
|
||||||
:reset-counter="resetCounter"
|
:reset-counter="resetCounter"
|
||||||
|
:has-unsaved-changes="hasUnsavedChanges([hero.flags, unModifiedHero.flags],
|
||||||
|
[hero.auth, unModifiedHero.auth],
|
||||||
|
[hero.balance, unModifiedHero.balance],
|
||||||
|
[hero.secret, unModifiedHero.secret])"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<subscription-and-perks
|
<subscription-and-perks
|
||||||
:hero="hero"
|
:hero="hero"
|
||||||
|
:group-plans="groupPlans"
|
||||||
|
:has-unsaved-changes="hasUnsavedChanges([hero.purchased.plan,
|
||||||
|
unModifiedHero.purchased.plan])"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<cron-and-auth
|
<cron-and-auth
|
||||||
@@ -29,6 +36,7 @@
|
|||||||
<user-profile
|
<user-profile
|
||||||
:hero="hero"
|
:hero="hero"
|
||||||
:reset-counter="resetCounter"
|
:reset-counter="resetCounter"
|
||||||
|
:has-unsaved-changes="hasUnsavedChanges([hero.profile, unModifiedHero.profile])"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<party-and-quest
|
<party-and-quest
|
||||||
@@ -47,6 +55,12 @@
|
|||||||
:preferences="hero.preferences"
|
:preferences="hero.preferences"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<stats
|
||||||
|
:hero="hero"
|
||||||
|
:has-unsaved-changes="hasUnsavedChanges([hero.stats, unModifiedHero.stats])"
|
||||||
|
:reset-counter="resetCounter"
|
||||||
|
/>
|
||||||
|
|
||||||
<items-owned
|
<items-owned
|
||||||
:hero="hero"
|
:hero="hero"
|
||||||
:reset-counter="resetCounter"
|
:reset-counter="resetCounter"
|
||||||
@@ -67,8 +81,18 @@
|
|||||||
:reset-counter="resetCounter"
|
:reset-counter="resetCounter"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<user-history
|
||||||
|
:hero="hero"
|
||||||
|
:reset-counter="resetCounter"
|
||||||
|
/>
|
||||||
|
|
||||||
<contributor-details
|
<contributor-details
|
||||||
:hero="hero"
|
:hero="hero"
|
||||||
|
:hasUnsavedChanges="hasUnsavedChanges(
|
||||||
|
[hero.contributor, unModifiedHero.contributor],
|
||||||
|
[hero.permissions, unModifiedHero.permissions],
|
||||||
|
[hero.secret, unModifiedHero.secret],
|
||||||
|
)"
|
||||||
:reset-counter="resetCounter"
|
:reset-counter="resetCounter"
|
||||||
@clear-data="clearData"
|
@clear-data="clearData"
|
||||||
/>
|
/>
|
||||||
@@ -109,6 +133,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import isEqualWith from 'lodash/isEqualWith';
|
||||||
import BasicDetails from './basicDetails';
|
import BasicDetails from './basicDetails';
|
||||||
import ItemsOwned from './itemsOwned';
|
import ItemsOwned from './itemsOwned';
|
||||||
import CronAndAuth from './cronAndAuth';
|
import CronAndAuth from './cronAndAuth';
|
||||||
@@ -121,6 +146,8 @@ import Transactions from './transactions';
|
|||||||
import SubscriptionAndPerks from './subscriptionAndPerks';
|
import SubscriptionAndPerks from './subscriptionAndPerks';
|
||||||
import CustomizationsOwned from './customizationsOwned.vue';
|
import CustomizationsOwned from './customizationsOwned.vue';
|
||||||
import Achievements from './achievements.vue';
|
import Achievements from './achievements.vue';
|
||||||
|
import UserHistory from './userHistory.vue';
|
||||||
|
import Stats from './stats.vue';
|
||||||
|
|
||||||
import { userStateMixin } from '../../../mixins/userState';
|
import { userStateMixin } from '../../../mixins/userState';
|
||||||
|
|
||||||
@@ -135,6 +162,8 @@ export default {
|
|||||||
PrivilegesAndGems,
|
PrivilegesAndGems,
|
||||||
ContributorDetails,
|
ContributorDetails,
|
||||||
Transactions,
|
Transactions,
|
||||||
|
UserHistory,
|
||||||
|
Stats,
|
||||||
SubscriptionAndPerks,
|
SubscriptionAndPerks,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
Achievements,
|
Achievements,
|
||||||
@@ -148,8 +177,10 @@ export default {
|
|||||||
return {
|
return {
|
||||||
userIdentifier: '',
|
userIdentifier: '',
|
||||||
resetCounter: 0,
|
resetCounter: 0,
|
||||||
|
unModifiedHero: {},
|
||||||
hero: {},
|
hero: {},
|
||||||
party: {},
|
party: {},
|
||||||
|
groupPlans: [],
|
||||||
hasParty: false,
|
hasParty: false,
|
||||||
partyNotExistError: false,
|
partyNotExistError: false,
|
||||||
adminHasPrivForParty: true,
|
adminHasPrivForParty: true,
|
||||||
@@ -168,6 +199,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clearData () {
|
clearData () {
|
||||||
|
this.unModifiedHero = {};
|
||||||
this.hero = {};
|
this.hero = {};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -176,6 +208,7 @@ export default {
|
|||||||
this.$emit('changeUserIdentifier', id); // change user identifier in Admin Panel's form
|
this.$emit('changeUserIdentifier', id); // change user identifier in Admin Panel's form
|
||||||
|
|
||||||
this.hero = await this.$store.dispatch('hall:getHero', { uuid: id });
|
this.hero = await this.$store.dispatch('hall:getHero', { uuid: id });
|
||||||
|
this.unModifiedHero = JSON.parse(JSON.stringify(this.hero));
|
||||||
|
|
||||||
if (!this.hero.flags) {
|
if (!this.hero.flags) {
|
||||||
this.hero.flags = {
|
this.hero.flags = {
|
||||||
@@ -206,8 +239,38 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.hero.purchased.plan.planId === 'group_plan_auto') {
|
||||||
|
try {
|
||||||
|
this.groupPlans = await this.$store.dispatch('hall:getHeroGroupPlans', { heroId: this.hero._id });
|
||||||
|
} catch (e) {
|
||||||
|
this.groupPlans = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.resetCounter += 1; // tell child components to reinstantiate from scratch
|
this.resetCounter += 1; // tell child components to reinstantiate from scratch
|
||||||
},
|
},
|
||||||
|
hasUnsavedChanges (...comparisons) {
|
||||||
|
for (const index in comparisons) {
|
||||||
|
if (index && comparisons[index]) {
|
||||||
|
const objs = comparisons[index];
|
||||||
|
const obj1 = objs[0];
|
||||||
|
const obj2 = objs[1];
|
||||||
|
if (!isEqualWith(obj1, obj2, (x, y) => {
|
||||||
|
if (typeof x === 'object' && typeof y === 'object') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (x === false && y === undefined) {
|
||||||
|
// Special case for checkboxes
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return x == y; // eslint-disable-line eqeqeq
|
||||||
|
})) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -269,16 +269,19 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async saveItem (item) {
|
async saveItem (item) {
|
||||||
// prepare the item's new value and path for being saved
|
// prepare the item's new value and path for being saved
|
||||||
this.hero.itemPath = item.path;
|
const toSave = {
|
||||||
|
_id: this.hero._id,
|
||||||
|
};
|
||||||
|
toSave.itemPath = item.path;
|
||||||
if (item.value === null) {
|
if (item.value === null) {
|
||||||
this.hero.itemVal = 'null';
|
toSave.itemVal = 'null';
|
||||||
} else if (item.value === false) {
|
} else if (item.value === false) {
|
||||||
this.hero.itemVal = 'false';
|
toSave.itemVal = 'false';
|
||||||
} else {
|
} else {
|
||||||
this.hero.itemVal = item.value;
|
toSave.itemVal = item.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveHero({ hero: this.hero, msg: item.key });
|
await this.saveHero({ hero: toSave, msg: item.key });
|
||||||
item.neverOwned = false;
|
item.neverOwned = false;
|
||||||
item.modified = false;
|
item.modified = false;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,22 +31,41 @@
|
|||||||
v-html="questErrors"
|
v-html="questErrors"
|
||||||
></p>
|
></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="userHasParty">
|
||||||
<div>
|
<div class="form-group row">
|
||||||
Party:
|
<label class="col-sm-3 col-form-label">
|
||||||
<span v-if="userHasParty">
|
Party ID
|
||||||
yes: party ID {{ groupPartyData._id }},
|
</label>
|
||||||
member count {{ groupPartyData.memberCount }} (may be wrong)
|
<strong class="col-sm-9 col-form-label">
|
||||||
<br>
|
{{ groupPartyData._id }}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Estimated Member Count
|
||||||
|
</label>
|
||||||
|
<strong class="col-sm-9 col-form-label">
|
||||||
|
{{ groupPartyData.memberCount }}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Leader
|
||||||
|
</label>
|
||||||
|
<strong class="col-sm-9 col-form-label">
|
||||||
<span v-if="userIsPartyLeader">User is the party leader</span>
|
<span v-if="userIsPartyLeader">User is the party leader</span>
|
||||||
<span v-else>Party leader is
|
<span v-else>Party leader is
|
||||||
<router-link :to="{'name': 'userProfile', 'params': {'userId': groupPartyData.leader}}">
|
<router-link :to="{'name': 'userProfile', 'params': {'userId': groupPartyData.leader}}">
|
||||||
{{ groupPartyData.leader }}
|
{{ groupPartyData.leader }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</strong>
|
||||||
<span v-else>no</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="btn btn-danger"
|
||||||
|
@click="removeFromParty()">Remove from Party</div>
|
||||||
|
</div>
|
||||||
|
<strong v-else>User is not in a party.</strong>
|
||||||
<div class="subsection-start">
|
<div class="subsection-start">
|
||||||
<p v-html="questStatus"></p>
|
<p v-html="questStatus"></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,6 +75,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as quests from '@/../../common/script/content/quests';
|
import * as quests from '@/../../common/script/content/quests';
|
||||||
|
import saveHero from '../mixins/saveHero';
|
||||||
|
|
||||||
function determineQuestStatus (self) {
|
function determineQuestStatus (self) {
|
||||||
// Quest data is in the user doc and party doc. They can be out of sync.
|
// Quest data is in the user doc and party doc. They can be out of sync.
|
||||||
@@ -271,6 +291,7 @@ function resetData (self) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [saveHero],
|
||||||
props: {
|
props: {
|
||||||
resetCounter: {
|
resetCounter: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -318,5 +339,14 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
resetData(this);
|
resetData(this);
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
removeFromParty () {
|
||||||
|
this.saveHero({
|
||||||
|
hero: { _id: this.userId, removeFromParty: true },
|
||||||
|
msg: 'Removed from party',
|
||||||
|
reloadData: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="saveHero({hero, msg: 'Privileges or Gems or Moderation Notes'})">
|
<form @submit.prevent="saveHero({hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
flags: hero.flags,
|
||||||
|
balance: hero.balance,
|
||||||
|
auth: hero.auth,
|
||||||
|
secret: hero.secret,
|
||||||
|
}, msg: 'Privileges or Gems or Moderation Notes'})">
|
||||||
<div class="card mt-2">
|
<div class="card mt-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
@@ -8,6 +14,9 @@
|
|||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
Privileges, Gem Balance
|
Privileges, Gem Balance
|
||||||
|
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -117,13 +126,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="expand"
|
v-if="expand"
|
||||||
class="card-footer"
|
class="card-footer d-flex align-items-center justify-content-between"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Save"
|
value="Save"
|
||||||
class="btn btn-primary mt-1"
|
class="btn btn-primary mt-1"
|
||||||
>
|
>
|
||||||
|
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -169,6 +181,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
hasUnsavedChanges: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label
|
||||||
|
class="col-sm-3 col-form-label"
|
||||||
|
:class="color"
|
||||||
|
>{{ label }}</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input
|
||||||
|
:value="value"
|
||||||
|
class="form-control"
|
||||||
|
type="number"
|
||||||
|
:step="step"
|
||||||
|
:max="max"
|
||||||
|
:min="min"
|
||||||
|
@input="$emit('input', parseInt($event.target.value, 10))"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.about-row {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-label {
|
||||||
|
color: $red_100;
|
||||||
|
}
|
||||||
|
.blue-label {
|
||||||
|
color: $blue_100;
|
||||||
|
}
|
||||||
|
.purple-label {
|
||||||
|
color: $purple_300;
|
||||||
|
}
|
||||||
|
.yellow-label {
|
||||||
|
color: $yellow_50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
model: {
|
||||||
|
prop: 'value',
|
||||||
|
event: 'input',
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'text-label',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
type: String,
|
||||||
|
default: 'any',
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
286
website/client/src/components/admin-panel/user-support/stats.vue
Normal file
286
website/client/src/components/admin-panel/user-support/stats.vue
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
<template>
|
||||||
|
<form @submit.prevent="submitClicked()">
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3
|
||||||
|
class="mb-0 mt-0"
|
||||||
|
:class="{'open': expand}"
|
||||||
|
@click="expand = !expand"
|
||||||
|
>
|
||||||
|
Stats
|
||||||
|
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
|
<stats-row
|
||||||
|
label="Health"
|
||||||
|
color="red-label"
|
||||||
|
:max="maxHealth"
|
||||||
|
v-model="hero.stats.hp" />
|
||||||
|
<stats-row
|
||||||
|
label="Experience"
|
||||||
|
color="yellow-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxFieldHardCap"
|
||||||
|
v-model="hero.stats.exp" />
|
||||||
|
<stats-row
|
||||||
|
label="Mana"
|
||||||
|
color="blue-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxFieldHardCap"
|
||||||
|
v-model="hero.stats.mp" />
|
||||||
|
<stats-row
|
||||||
|
label="Level"
|
||||||
|
step="1"
|
||||||
|
min="0"
|
||||||
|
:max="maxLevelHardCap"
|
||||||
|
v-model="hero.stats.lvl" />
|
||||||
|
<stats-row
|
||||||
|
label="Gold"
|
||||||
|
min="0"
|
||||||
|
:max="maxFieldHardCap"
|
||||||
|
v-model="hero.stats.gp" />
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">Selected Class</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select
|
||||||
|
id="selectedClass"
|
||||||
|
v-model="hero.stats.class"
|
||||||
|
class="form-control"
|
||||||
|
:disabled="hero.stats.lvl < 10"
|
||||||
|
>
|
||||||
|
<option value="warrior">Warrior</option>
|
||||||
|
<option value="wizard">Mage</option>
|
||||||
|
<option value="healer">Healer</option>
|
||||||
|
<option value="rogue">Rogue</option>
|
||||||
|
</select>
|
||||||
|
<small>
|
||||||
|
When changing class, players usually need stat points deallocated as well.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Stat Points</h3>
|
||||||
|
<stats-row
|
||||||
|
label="Unallocated"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
v-model="hero.stats.points" />
|
||||||
|
<stats-row
|
||||||
|
label="Strength"
|
||||||
|
color="red-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.str" />
|
||||||
|
<stats-row
|
||||||
|
label="Intelligence"
|
||||||
|
color="blue-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.int" />
|
||||||
|
<stats-row
|
||||||
|
label="Perception"
|
||||||
|
color="purple-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.per" />
|
||||||
|
<stats-row
|
||||||
|
label="Constitution"
|
||||||
|
color="yellow-label"
|
||||||
|
min="0"
|
||||||
|
:max="maxStatPoints"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.con" />
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="offset-sm-3 col-sm-9">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-warning btn-sm"
|
||||||
|
@click="deallocateStatPoints">
|
||||||
|
Deallocate all stat points
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row" v-if="statPointsIncorrect">
|
||||||
|
<div class="offset-sm-3 col-sm-9 text-danger">
|
||||||
|
Error: Sum of stat points should equal the users level
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Buffs</h3>
|
||||||
|
<stats-row
|
||||||
|
label="Strength"
|
||||||
|
color="red-label"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.buffs.str" />
|
||||||
|
<stats-row
|
||||||
|
label="Intelligence"
|
||||||
|
color="blue-label"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.buffs.int" />
|
||||||
|
<stats-row
|
||||||
|
label="Perception"
|
||||||
|
color="purple-label"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.buffs.per" />
|
||||||
|
<stats-row
|
||||||
|
label="Constitution"
|
||||||
|
color="yellow-label"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="hero.stats.buffs.con" />
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="offset-sm-3 col-sm-9">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-warning btn-sm"
|
||||||
|
@click="resetBuffs">
|
||||||
|
Reset Buffs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-footer d-flex align-items-center justify-content-between"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Save"
|
||||||
|
class="btn btn-primary mt-1"
|
||||||
|
>
|
||||||
|
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.about-row {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
MAX_HEALTH,
|
||||||
|
MAX_STAT_POINTS,
|
||||||
|
MAX_LEVEL_HARD_CAP,
|
||||||
|
MAX_FIELD_HARD_CAP,
|
||||||
|
} from '@/../../common/script/constants';
|
||||||
|
import markdownDirective from '@/directives/markdown';
|
||||||
|
import saveHero from '../mixins/saveHero';
|
||||||
|
|
||||||
|
import { mapState } from '@/libs/store';
|
||||||
|
import { userStateMixin } from '../../../mixins/userState';
|
||||||
|
|
||||||
|
import StatsRow from './stats-row';
|
||||||
|
|
||||||
|
function resetData (self) {
|
||||||
|
self.expand = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
directives: {
|
||||||
|
markdown: markdownDirective,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
StatsRow,
|
||||||
|
},
|
||||||
|
mixins: [
|
||||||
|
userStateMixin,
|
||||||
|
saveHero,
|
||||||
|
],
|
||||||
|
computed: {
|
||||||
|
...mapState({ user: 'user.data' }),
|
||||||
|
statPointsIncorrect () {
|
||||||
|
if (this.hero.stats.lvl >= 10) {
|
||||||
|
return (parseInt(this.hero.stats.points, 10)
|
||||||
|
+ parseInt(this.hero.stats.str, 10)
|
||||||
|
+ parseInt(this.hero.stats.int, 10)
|
||||||
|
+ parseInt(this.hero.stats.per, 10)
|
||||||
|
+ parseInt(this.hero.stats.con, 10)
|
||||||
|
) !== this.hero.stats.lvl;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
resetCounter: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
hasUnsavedChanges: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
expand: false,
|
||||||
|
maxHealth: MAX_HEALTH,
|
||||||
|
maxStatPoints: MAX_STAT_POINTS,
|
||||||
|
maxLevelHardCap: MAX_LEVEL_HARD_CAP,
|
||||||
|
maxFieldHardCap: MAX_FIELD_HARD_CAP,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
resetCounter () {
|
||||||
|
resetData(this);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
resetData(this);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitClicked () {
|
||||||
|
if (this.statPointsIncorrect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.saveHero({
|
||||||
|
hero: {
|
||||||
|
_id: this.hero._id,
|
||||||
|
stats: this.hero.stats,
|
||||||
|
},
|
||||||
|
msg: 'Stats',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetBuffs () {
|
||||||
|
this.hero.stats.buffs = {
|
||||||
|
str: 0,
|
||||||
|
int: 0,
|
||||||
|
per: 0,
|
||||||
|
con: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
deallocateStatPoints () {
|
||||||
|
this.hero.stats.points = this.hero.stats.lvl;
|
||||||
|
this.hero.stats.str = 0;
|
||||||
|
this.hero.stats.int = 0;
|
||||||
|
this.hero.stats.per = 0;
|
||||||
|
this.hero.stats.con = 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,30 +1,135 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="saveHero({ hero, msg: 'Subscription Perks' })">
|
<form
|
||||||
|
@submit.prevent="saveHero({ hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
purchased: hero.purchased
|
||||||
|
}, msg: 'Subscription Perks' })"
|
||||||
|
>
|
||||||
<div class="card mt-2">
|
<div class="card mt-2">
|
||||||
<div class="card-header">
|
<div class="card-header"
|
||||||
|
@click="expand = !expand">
|
||||||
<h3
|
<h3
|
||||||
class="mb-0 mt-0"
|
class="mb-0 mt-0"
|
||||||
:class="{ 'open': expand }"
|
:class="{ 'open': expand }"
|
||||||
@click="expand = !expand"
|
|
||||||
>
|
>
|
||||||
Subscription, Monthly Perks
|
Subscription, Monthly Perks
|
||||||
|
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="expand"
|
v-if="expand"
|
||||||
class="card-body"
|
class="card-body"
|
||||||
>
|
>
|
||||||
<div v-if="hero.purchased.plan.paymentMethod">
|
<div
|
||||||
Payment method:
|
class="form-group row"
|
||||||
<strong>{{ hero.purchased.plan.paymentMethod }}</strong>
|
>
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Payment method:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input v-model="hero.purchased.plan.paymentMethod"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
v-if="!isRegularPaymentMethod"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-else
|
||||||
|
v-model="hero.purchased.plan.paymentMethod"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<option value="groupPlan">Group Plan</option>
|
||||||
|
<option value="Stripe">Stripe</option>
|
||||||
|
<option value="Apple">Apple</option>
|
||||||
|
<option value="Google">Google</option>
|
||||||
|
<option value="Amazon Payments">Amazon</option>
|
||||||
|
<option value="PayPal">PayPal</option>
|
||||||
|
<option value="Gift">Gift</option>
|
||||||
|
<option value="">Clear out</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hero.purchased.plan.planId">
|
<div
|
||||||
Payment schedule ("basic-earned" is monthly):
|
class="form-group row"
|
||||||
<strong>{{ hero.purchased.plan.planId }}</strong>
|
>
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Payment schedule:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input v-model="hero.purchased.plan.planId"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
v-if="!isRegularPlanId"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-else
|
||||||
|
v-model="hero.purchased.plan.planId"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<option value="basic_earned">Monthly recurring</option>
|
||||||
|
<option value="basic_3mo">3 Months recurring</option>
|
||||||
|
<option value="basic_6mo">6 Months recurring</option>
|
||||||
|
<option value="basic_12mo">12 Months recurring</option>
|
||||||
|
<option value="group_monthly">Group Plan (legacy)</option>
|
||||||
|
<option value="group_plan_auto">Group Plan (auto)</option>
|
||||||
|
<option value="">Clear out</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hero.purchased.plan.planId == 'group_plan_auto'">
|
<div
|
||||||
Group plan ID:
|
class="form-group row"
|
||||||
<strong>{{ hero.purchased.plan.owner }}</strong>
|
>
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Customer ID:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input
|
||||||
|
v-model="hero.purchased.plan.customerId"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row"
|
||||||
|
v-if="hero.purchased.plan.planId === 'group_plan_auto'">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Group Plan Memberships:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9 col-form-label">
|
||||||
|
<loading-spinner
|
||||||
|
v-if="!groupPlans"
|
||||||
|
dark-color=true
|
||||||
|
/>
|
||||||
|
<b
|
||||||
|
v-else-if="groupPlans.length === 0"
|
||||||
|
class="text-danger col-form-label"
|
||||||
|
>User is not part of an active group plan!</b>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
v-for="group in groupPlans"
|
||||||
|
:key="group._id"
|
||||||
|
class="card mb-2">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title">{{ group.name }}
|
||||||
|
<small class="float-right">{{ group._id }}</small>
|
||||||
|
</h6>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>Leader: </strong>
|
||||||
|
<a
|
||||||
|
v-if="group.leader !== hero._id"
|
||||||
|
@click="switchUser(group.leader)"
|
||||||
|
>{{ group.leader }}</a>
|
||||||
|
<strong v-else class="text-success">This user</strong>
|
||||||
|
</p>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>Members: </strong> {{ group.memberCount }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="hero.purchased.plan.dateCreated"
|
v-if="hero.purchased.plan.dateCreated"
|
||||||
@@ -85,8 +190,18 @@
|
|||||||
<strong class="input-group-text">
|
<strong class="input-group-text">
|
||||||
{{ dateFormat(hero.purchased.plan.dateTerminated) }}
|
{{ dateFormat(hero.purchased.plan.dateTerminated) }}
|
||||||
</strong>
|
</strong>
|
||||||
|
<a class="btn btn-danger"
|
||||||
|
href="#"
|
||||||
|
v-b-modal.sub_termination_modal
|
||||||
|
v-if="!hero.purchased.plan.dateTerminated && hero.purchased.plan.planId">
|
||||||
|
Terminate
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<small v-if="!hero.purchased.plan.dateTerminated
|
||||||
|
&& hero.purchased.plan.planId" class="text-success">
|
||||||
|
The subscription does not have a termination date and is active.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
@@ -101,6 +216,35 @@
|
|||||||
min="0"
|
min="0"
|
||||||
step="1"
|
step="1"
|
||||||
>
|
>
|
||||||
|
<small class="text-secondary">
|
||||||
|
Cumulative subscribed months across subscription periods.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Extra months:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
v-model="hero.purchased.plan.extraMonths"
|
||||||
|
class="form-control"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="any"
|
||||||
|
>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<a class="btn btn-warning"
|
||||||
|
@click="applyExtraMonths"
|
||||||
|
v-if="hero.purchased.plan.dateTerminated && hero.purchased.plan.extraMonths > 0">
|
||||||
|
Apply Credit
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="text-secondary">
|
||||||
|
Additional credit that is applied if a subscription is cancelled.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
@@ -174,10 +318,6 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hero.purchased.plan.extraMonths > 0">
|
|
||||||
Additional credit (applied upon cancellation):
|
|
||||||
<strong>{{ hero.purchased.plan.extraMonths }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-3 col-form-label">
|
<label class="col-sm-3 col-form-label">
|
||||||
Mystery Items:
|
Mystery Items:
|
||||||
@@ -199,18 +339,64 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group row"
|
||||||
|
v-if="!isConvertingToGroupPlan && hero.purchased.plan.planId !== 'group_plan_auto'">
|
||||||
|
<div class="offset-sm-3 col-sm-9">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary btn-sm"
|
||||||
|
@click="beginGroupPlanConvert">
|
||||||
|
Begin converting to group plan subscription
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group row"
|
||||||
|
v-if="isConvertingToGroupPlan">
|
||||||
|
<label class="col-sm-3 col-form-label">
|
||||||
|
Group Plan group ID:
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input
|
||||||
|
v-model="groupPlanID"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="expand"
|
v-if="expand"
|
||||||
class="card-footer"
|
class="card-footer d-flex align-items-center justify-content-between"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Save"
|
value="Save"
|
||||||
class="btn btn-primary mt-1"
|
class="btn btn-primary mt-1"
|
||||||
|
@click="saveClicked"
|
||||||
>
|
>
|
||||||
|
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<b-modal id="sub_termination_modal" title="Set Termination Date">
|
||||||
|
<p>
|
||||||
|
You can set the sub benefit termination date to today or to the last
|
||||||
|
day of the current billing cycle. Any extra subscription credit will
|
||||||
|
then be processed and automatically added onto the selected date.
|
||||||
|
</p>
|
||||||
|
<template #modal-footer>
|
||||||
|
<div class="mt-3 btn btn-secondary" @click="$bvModal.hide('sub_termination_modal')">
|
||||||
|
Close
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 btn btn-danger" @click="terminateSubscription()">
|
||||||
|
Set to Today
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 btn btn-danger" @click="terminateSubscription(todayWithRemainingCycle)">
|
||||||
|
Set to {{ todayWithRemainingCycle.utc().format('MM/DD/YYYY') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</b-modal>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -231,21 +417,38 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import isUUID from 'validator/es/lib/isUUID';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { getPlanContext } from '@/../../common/script/cron';
|
import { getPlanContext } from '@/../../common/script/cron';
|
||||||
import saveHero from '../mixins/saveHero';
|
import saveHero from '../mixins/saveHero';
|
||||||
|
import subscriptionBlocks from '../../../../../common/script/content/subscriptionBlocks';
|
||||||
|
import LoadingSpinner from '@/components/ui/loadingSpinner';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [saveHero],
|
mixins: [saveHero],
|
||||||
|
components: {
|
||||||
|
LoadingSpinner,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
hero: {
|
hero: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
hasUnsavedChanges: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
groupPlans: {
|
||||||
|
type: Array,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
expand: false,
|
expand: false,
|
||||||
|
isConvertingToGroupPlan: false,
|
||||||
|
groupPlanID: '',
|
||||||
|
subscriptionBlocks,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -255,6 +458,30 @@ export default {
|
|||||||
if (!currentPlanContext.nextHourglassDate) return 'N/A';
|
if (!currentPlanContext.nextHourglassDate) return 'N/A';
|
||||||
return currentPlanContext.nextHourglassDate.format('MMMM YYYY');
|
return currentPlanContext.nextHourglassDate.format('MMMM YYYY');
|
||||||
},
|
},
|
||||||
|
isRegularPlanId () {
|
||||||
|
return this.subscriptionBlocks[this.hero.purchased.plan.planId] !== undefined;
|
||||||
|
},
|
||||||
|
isRegularPaymentMethod () {
|
||||||
|
return [
|
||||||
|
'groupPlan',
|
||||||
|
'Group Plan',
|
||||||
|
'Stripe',
|
||||||
|
'Apple',
|
||||||
|
'Google',
|
||||||
|
'Amazon Payments',
|
||||||
|
'PayPal',
|
||||||
|
'Gift',
|
||||||
|
].includes(this.hero.purchased.plan.paymentMethod);
|
||||||
|
},
|
||||||
|
todayWithRemainingCycle () {
|
||||||
|
const now = moment();
|
||||||
|
const monthCount = subscriptionBlocks[this.hero.purchased.plan.planId].months;
|
||||||
|
const terminationDate = moment(this.hero.purchased.plan.dateCurrentTypeCreated || new Date());
|
||||||
|
while (terminationDate.isBefore(now)) {
|
||||||
|
terminationDate.add(monthCount, 'months');
|
||||||
|
}
|
||||||
|
return terminationDate;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
dateFormat (date) {
|
dateFormat (date) {
|
||||||
@@ -263,6 +490,46 @@ export default {
|
|||||||
}
|
}
|
||||||
return moment(date).format('YYYY/MM/DD');
|
return moment(date).format('YYYY/MM/DD');
|
||||||
},
|
},
|
||||||
|
terminateSubscription (terminationDate) {
|
||||||
|
if (terminationDate) {
|
||||||
|
this.hero.purchased.plan.dateTerminated = terminationDate.utc().format();
|
||||||
|
} else {
|
||||||
|
this.hero.purchased.plan.dateTerminated = moment(new Date()).utc().format();
|
||||||
|
}
|
||||||
|
this.applyExtraMonths();
|
||||||
|
this.saveHero({ hero: this.hero, msg: 'Subscription Termination', reloadData: true });
|
||||||
|
},
|
||||||
|
applyExtraMonths () {
|
||||||
|
if (this.hero.purchased.plan.extraMonths > 0 || this.hero.purchased.plan.extraMonths !== '0') {
|
||||||
|
const date = moment(this.hero.purchased.plan.dateTerminated || new Date());
|
||||||
|
const extraMonths = Math.max(this.hero.purchased.plan.extraMonths, 0);
|
||||||
|
const extraDays = Math.ceil(30.5 * extraMonths);
|
||||||
|
this.hero.purchased.plan.dateTerminated = date.add(extraDays, 'days').utc().format();
|
||||||
|
this.hero.purchased.plan.extraMonths = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beginGroupPlanConvert () {
|
||||||
|
this.isConvertingToGroupPlan = true;
|
||||||
|
this.hero.purchased.plan.owner = '';
|
||||||
|
},
|
||||||
|
saveClicked (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.isConvertingToGroupPlan) {
|
||||||
|
if (!isUUID(this.groupPlanID)) {
|
||||||
|
alert('Invalid group ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hero.purchased.plan.convertToGroupPlan = this.groupPlanID;
|
||||||
|
this.saveHero({ hero: this.hero, msg: 'Group Plan Subscription', reloadData: true });
|
||||||
|
} else {
|
||||||
|
this.saveHero({ hero: this.hero, msg: 'Subscription Perks', reloadData: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
switchUser (id) {
|
||||||
|
if (window.confirm('Switch to this user?')) {
|
||||||
|
this.$emit('changeUserIdentifier', id);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card mt-2">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3
|
||||||
|
class="mb-0 mt-0"
|
||||||
|
:class="{'open': expand}"
|
||||||
|
@click="toggleHistoryOpen"
|
||||||
|
>
|
||||||
|
User History
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="expand"
|
||||||
|
class="card-body"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="clearfix">
|
||||||
|
<div class="mb-4 float-left">
|
||||||
|
<button
|
||||||
|
class="page-header btn-flat tab-button textCondensed"
|
||||||
|
:class="{'active': selectedTab === 'armoire'}"
|
||||||
|
@click="selectTab('armoire')"
|
||||||
|
>
|
||||||
|
Armoire
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="page-header btn-flat tab-button textCondensed"
|
||||||
|
:class="{'active': selectedTab === 'questInvites'}"
|
||||||
|
@click="selectTab('questInvites')"
|
||||||
|
>
|
||||||
|
Quest Invitations
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="page-header btn-flat tab-button textCondensed"
|
||||||
|
:class="{'active': selectedTab === 'cron'}"
|
||||||
|
@click="selectTab('cron')"
|
||||||
|
>
|
||||||
|
Cron
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div
|
||||||
|
v-if="selectedTab === 'armoire'"
|
||||||
|
class="col-12"
|
||||||
|
>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
{{ $t('timestamp') }}
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Client
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
Received
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="entry in armoire"
|
||||||
|
:key="entry.timestamp"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
v-b-tooltip.hover="entry.timestamp"
|
||||||
|
>{{ entry.timestamp | timeAgo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ entry.client }}</td>
|
||||||
|
<td>{{ entry.reward }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedTab === 'questInvites'"
|
||||||
|
class="col-12"
|
||||||
|
>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
{{ $t('timestamp') }}
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Client
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Quest Key
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Response
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="entry in questInviteResponses"
|
||||||
|
:key="entry.timestamp"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
v-b-tooltip.hover="entry.timestamp"
|
||||||
|
>{{ entry.timestamp | timeAgo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ entry.client }}</td>
|
||||||
|
<td>{{ entry.quest }}</td>
|
||||||
|
<td>{{ questInviteResponseText(entry.response) }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="selectedTab === 'cron'"
|
||||||
|
class="col-12"
|
||||||
|
>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
v-once
|
||||||
|
>
|
||||||
|
{{ $t('timestamp') }}
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Client
|
||||||
|
</th>
|
||||||
|
<th v-once>
|
||||||
|
Checkin Count
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="entry in cron"
|
||||||
|
:key="entry.timestamp"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
v-b-tooltip.hover="entry.timestamp"
|
||||||
|
>{{ entry.timestamp | timeAgo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ entry.client }}</td>
|
||||||
|
<td>{{ entry.checkinCount }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.page-header.btn-flat {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
height: 2rem;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-stretch: condensed;
|
||||||
|
line-height: 1.33;
|
||||||
|
letter-spacing: normal;
|
||||||
|
color: $gray-10;
|
||||||
|
|
||||||
|
margin-right: 1.125rem;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 2.5rem;
|
||||||
|
|
||||||
|
&.active, &:hover {
|
||||||
|
color: $purple-300;
|
||||||
|
box-shadow: 0px -0.25rem 0px $purple-300 inset;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
import { userStateMixin } from '../../../mixins/userState';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
filters: {
|
||||||
|
timeAgo (value) {
|
||||||
|
return moment(value).fromNow();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mixins: [userStateMixin],
|
||||||
|
props: {
|
||||||
|
hero: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
resetCounter: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
expand: false,
|
||||||
|
selectedTab: 'armoire',
|
||||||
|
armoire: [],
|
||||||
|
questInviteResponses: [],
|
||||||
|
cron: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
resetCounter () {
|
||||||
|
if (this.expand) {
|
||||||
|
this.retrieveUserHistory();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectTab (type) {
|
||||||
|
this.selectedTab = type;
|
||||||
|
},
|
||||||
|
async toggleHistoryOpen () {
|
||||||
|
this.expand = !this.expand;
|
||||||
|
if (this.expand) {
|
||||||
|
this.retrieveUserHistory();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async retrieveUserHistory () {
|
||||||
|
const history = await this.$store.dispatch('adminPanel:getUserHistory', { userIdentifier: this.hero._id });
|
||||||
|
this.armoire = history.armoire;
|
||||||
|
this.questInviteResponses = history.questInviteResponses;
|
||||||
|
this.cron = history.cron;
|
||||||
|
},
|
||||||
|
questInviteResponseText (response) {
|
||||||
|
if (response === 'accept') {
|
||||||
|
return 'Accepted';
|
||||||
|
}
|
||||||
|
if (response === 'reject') {
|
||||||
|
return 'Rejected';
|
||||||
|
}
|
||||||
|
if (response === 'leave') {
|
||||||
|
return 'Left active quest';
|
||||||
|
}
|
||||||
|
if (response === 'invite') {
|
||||||
|
return 'Accepted as owner';
|
||||||
|
}
|
||||||
|
if (response === 'abort') {
|
||||||
|
return 'Aborted by owner';
|
||||||
|
}
|
||||||
|
if (response === 'abortByLeader') {
|
||||||
|
return 'Aborted by party leader';
|
||||||
|
}
|
||||||
|
if (response === 'cancel') {
|
||||||
|
return 'Cancelled before start';
|
||||||
|
}
|
||||||
|
if (response === 'cancelByLeader') {
|
||||||
|
return 'Cancelled before start by party leader';
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="saveHero({hero, msg: 'Users Profile'})">
|
<form
|
||||||
|
@submit.prevent="saveHero({hero: {
|
||||||
|
_id: hero._id,
|
||||||
|
profile: hero.profile
|
||||||
|
}, msg: 'Users Profile'})"
|
||||||
|
>
|
||||||
<div class="card mt-2">
|
<div class="card mt-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3
|
<h3
|
||||||
@@ -8,6 +13,9 @@
|
|||||||
@click="expand = !expand"
|
@click="expand = !expand"
|
||||||
>
|
>
|
||||||
User Profile
|
User Profile
|
||||||
|
<b v-if="hasUnsavedChanges && !expand" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -51,13 +59,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="expand"
|
v-if="expand"
|
||||||
class="card-footer"
|
class="card-footer d-flex align-items-center justify-content-between"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Save"
|
value="Save"
|
||||||
class="btn btn-primary mt-1"
|
class="btn btn-primary mt-1"
|
||||||
>
|
>
|
||||||
|
<b v-if="hasUnsavedChanges" class="text-warning float-right">
|
||||||
|
Unsaved changes
|
||||||
|
</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -101,6 +112,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
hasUnsavedChanges: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -37,9 +37,9 @@
|
|||||||
<h3>{{ $t('footerCompany') }}</h3>
|
<h3>{{ $t('footerCompany') }}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<router-link to="/static/contact">
|
<a href="mailto:admin@habitica.com">
|
||||||
{{ $t('contactUs') }}
|
{{ $t('contactUs') }}
|
||||||
</router-link>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link to="/static/press-kit">
|
<router-link to="/static/press-kit">
|
||||||
@@ -55,9 +55,9 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://habitica.fandom.com/wiki/Whats_New"
|
@click="showBailey()"
|
||||||
target="_blank"
|
>
|
||||||
>{{ $t('oldNews') }}
|
{{ $t('oldNews') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://habitica.fandom.com/wiki/Contributing_to_Habitica"
|
href="https://github.com/HabitRPG/habitica/wiki/Contributing-to-Habitica"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>{{ $t('companyContribute') }}
|
>{{ $t('companyContribute') }}
|
||||||
</a>
|
</a>
|
||||||
@@ -131,13 +131,6 @@
|
|||||||
>{{ $t('requestFeature') }}
|
>{{ $t('requestFeature') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://habitica.fandom.com/"
|
|
||||||
target="_blank"
|
|
||||||
>{{ $t('wiki') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- Developers -->
|
<!-- Developers -->
|
||||||
@@ -165,13 +158,6 @@
|
|||||||
>{{ $t('guidanceForBlacksmiths') }}
|
>{{ $t('guidanceForBlacksmiths') }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://habitica.fandom.com/wiki/Extensions,_Add-Ons,_and_Customizations"
|
|
||||||
target="_blank"
|
|
||||||
>{{ $t('communityExtensions') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -212,12 +198,12 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="social-circle"
|
class="social-circle"
|
||||||
href="https://twitter.com/habitica/"
|
href="https://bsky.app/profile/habitica.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="social-icon svg-icon twitter"
|
class="social-icon svg-icon bluesky"
|
||||||
v-html="icons.twitter"
|
v-html="icons.bluesky"
|
||||||
></div>
|
></div>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
@@ -525,7 +511,7 @@ footer {
|
|||||||
background-color: $gray-500;
|
background-color: $gray-500;
|
||||||
color: $gray-50;
|
color: $gray-50;
|
||||||
padding: 32px 142px 40px;
|
padding: 32px 142px 40px;
|
||||||
a {
|
a, a:not([href]) {
|
||||||
color: $gray-50;
|
color: $gray-50;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
@@ -814,7 +800,7 @@ h3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.twitter svg {
|
.bluesky svg {
|
||||||
background-color: #e1e0e3;
|
background-color: #e1e0e3;
|
||||||
fill: #878190;
|
fill: #878190;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -853,7 +839,7 @@ import Vue from 'vue';
|
|||||||
|
|
||||||
// images
|
// images
|
||||||
import melior from '@/assets/svg/melior.svg';
|
import melior from '@/assets/svg/melior.svg';
|
||||||
import twitter from '@/assets/svg/twitter.svg';
|
import bluesky from '@/assets/svg/bluesky.svg';
|
||||||
import facebook from '@/assets/svg/facebook.svg';
|
import facebook from '@/assets/svg/facebook.svg';
|
||||||
import instagram from '@/assets/svg/instagram.svg';
|
import instagram from '@/assets/svg/instagram.svg';
|
||||||
import tumblr from '@/assets/svg/tumblr.svg';
|
import tumblr from '@/assets/svg/tumblr.svg';
|
||||||
@@ -885,7 +871,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
melior,
|
melior,
|
||||||
twitter,
|
bluesky,
|
||||||
facebook,
|
facebook,
|
||||||
instagram,
|
instagram,
|
||||||
tumblr,
|
tumblr,
|
||||||
@@ -958,24 +944,28 @@ export default {
|
|||||||
},
|
},
|
||||||
async jumpTime (amount) {
|
async jumpTime (amount) {
|
||||||
const response = await axios.post('/api/v4/debug/jump-time', { offsetDays: amount });
|
const response = await axios.post('/api/v4/debug/jump-time', { offsetDays: amount });
|
||||||
if (amount > 0) {
|
setTimeout(() => {
|
||||||
Vue.config.clock.jump(amount * 24 * 60 * 60 * 1000);
|
if (amount > 0) {
|
||||||
} else {
|
Vue.config.clock.jump(amount * 24 * 60 * 60 * 1000);
|
||||||
Vue.config.clock.setSystemTime(moment().add(amount, 'days').toDate());
|
} else {
|
||||||
}
|
Vue.config.clock.setSystemTime(moment().add(amount, 'days').toDate());
|
||||||
this.lastTimeJump = response.data.data.time;
|
}
|
||||||
this.triggerGetWorldState(true);
|
this.lastTimeJump = response.data.data.time;
|
||||||
|
this.triggerGetWorldState(true);
|
||||||
|
}, 1000);
|
||||||
},
|
},
|
||||||
async resetTime () {
|
async resetTime () {
|
||||||
const response = await axios.post('/api/v4/debug/jump-time', { reset: true });
|
const response = await axios.post('/api/v4/debug/jump-time', { reset: true });
|
||||||
const time = new Date(response.data.data.time);
|
const time = new Date(response.data.data.time);
|
||||||
Vue.config.clock.restore();
|
setTimeout(() => {
|
||||||
Vue.config.clock = sinon.useFakeTimers({
|
Vue.config.clock.restore();
|
||||||
now: time,
|
Vue.config.clock = sinon.useFakeTimers({
|
||||||
shouldAdvanceTime: true,
|
now: time,
|
||||||
});
|
shouldAdvanceTime: true,
|
||||||
this.lastTimeJump = response.data.data.time;
|
});
|
||||||
this.triggerGetWorldState(true);
|
this.lastTimeJump = response.data.data.time;
|
||||||
|
this.triggerGetWorldState(true);
|
||||||
|
}, 1000);
|
||||||
},
|
},
|
||||||
addExp () {
|
addExp () {
|
||||||
// @TODO: Name these variables better
|
// @TODO: Name these variables better
|
||||||
@@ -1003,7 +993,6 @@ export default {
|
|||||||
async bossRage () {
|
async bossRage () {
|
||||||
await axios.post('/api/v4/debug/boss-rage');
|
await axios.post('/api/v4/debug/boss-rage');
|
||||||
},
|
},
|
||||||
|
|
||||||
async makeAdmin () {
|
async makeAdmin () {
|
||||||
await axios.post('/api/v4/debug/make-admin');
|
await axios.post('/api/v4/debug/make-admin');
|
||||||
// @TODO: Notification.text('You are now an admin!
|
// @TODO: Notification.text('You are now an admin!
|
||||||
@@ -1013,6 +1002,9 @@ export default {
|
|||||||
donate () {
|
donate () {
|
||||||
this.$root.$emit('bv::show::modal', 'buy-gems', { alreadyTracked: true });
|
this.$root.$emit('bv::show::modal', 'buy-gems', { alreadyTracked: true });
|
||||||
},
|
},
|
||||||
|
showBailey () {
|
||||||
|
this.$root.$emit('bv::show::modal', 'new-stuff');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
v-if="member.preferences"
|
v-if="member.preferences"
|
||||||
class="avatar"
|
class="avatar"
|
||||||
:style="{width, height, paddingTop}"
|
:style="{width, height, paddingTop}"
|
||||||
:class="backgroundClass"
|
:class="topLevelClassList"
|
||||||
@click.prevent="castEnd()"
|
@click.prevent="castEnd()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -55,7 +55,11 @@
|
|||||||
<span :class="[getGearClass('eyewear'), specialMountClass]"></span>
|
<span :class="[getGearClass('eyewear'), specialMountClass]"></span>
|
||||||
<span :class="[getGearClass('head'), specialMountClass]"></span>
|
<span :class="[getGearClass('head'), specialMountClass]"></span>
|
||||||
<span :class="[getGearClass('headAccessory'), specialMountClass]"></span>
|
<span :class="[getGearClass('headAccessory'), specialMountClass]"></span>
|
||||||
<span :class="['hair_flower_' + member.preferences.hair.flower, specialMountClass]"></span>
|
<span
|
||||||
|
:class="[
|
||||||
|
'hair_flower_' + member.preferences.hair.flower, specialMountClass
|
||||||
|
]"
|
||||||
|
></span>
|
||||||
<span
|
<span
|
||||||
v-if="!hideGear('shield')"
|
v-if="!hideGear('shield')"
|
||||||
:class="[getGearClass('shield'), specialMountClass]"
|
:class="[getGearClass('shield'), specialMountClass]"
|
||||||
@@ -63,6 +67,7 @@
|
|||||||
<span
|
<span
|
||||||
v-if="!hideGear('weapon')"
|
v-if="!hideGear('weapon')"
|
||||||
:class="[getGearClass('weapon'), specialMountClass]"
|
:class="[getGearClass('weapon'), specialMountClass]"
|
||||||
|
class="weapon"
|
||||||
></span>
|
></span>
|
||||||
</template>
|
</template>
|
||||||
<!-- Resting-->
|
<!-- Resting-->
|
||||||
@@ -96,15 +101,23 @@
|
|||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 141px;
|
width: 141px;
|
||||||
height: 147px;
|
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.centered-avatar {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetting the additional padding
|
||||||
|
margin-bottom: -0.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-sprites {
|
.character-sprites {
|
||||||
width: 90px;
|
width: 90px;
|
||||||
height: 90px;
|
height: 90px;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-sprites span {
|
.character-sprites span {
|
||||||
@@ -123,22 +136,49 @@
|
|||||||
.invert {
|
.invert {
|
||||||
filter: invert(100%);
|
filter: invert(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.debug {
|
||||||
|
border: 1px solid red;
|
||||||
|
|
||||||
|
.character-sprites {
|
||||||
|
border: 1px solid blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weapon {
|
||||||
|
border: 1px solid green;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
border: 1px solid yellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import some from 'lodash/some';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import foolPet from '../mixins/foolPet';
|
import foolPet from '../mixins/foolPet';
|
||||||
|
|
||||||
import ClassBadge from '@/components/members/classBadge';
|
import ClassBadge from '@/components/members/classBadge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO replace avatarOnly with multiple options like
|
||||||
|
* - showMount
|
||||||
|
* - showPet
|
||||||
|
* - showBackground
|
||||||
|
* - showWeapons
|
||||||
|
*/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ClassBadge,
|
ClassBadge,
|
||||||
},
|
},
|
||||||
mixins: [foolPet],
|
mixins: [foolPet],
|
||||||
props: {
|
props: {
|
||||||
|
debugMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
member: {
|
member: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -156,14 +196,21 @@ export default {
|
|||||||
},
|
},
|
||||||
overrideAvatarGear: {
|
overrideAvatarGear: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
default (data) {
|
||||||
|
return data;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
type: Number,
|
type: String,
|
||||||
default: 140,
|
default: '141px',
|
||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
type: Number,
|
type: String,
|
||||||
default: 147,
|
default: '147px',
|
||||||
|
},
|
||||||
|
centerAvatar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
spritesMargin: {
|
spritesMargin: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -171,11 +218,16 @@ export default {
|
|||||||
},
|
},
|
||||||
overrideTopPadding: {
|
overrideTopPadding: {
|
||||||
type: String,
|
type: String,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
showVisualBuffs: {
|
showVisualBuffs: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
showWeapon: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
@@ -204,6 +256,19 @@ export default {
|
|||||||
|
|
||||||
return val;
|
return val;
|
||||||
},
|
},
|
||||||
|
topLevelClassList () {
|
||||||
|
const classes = [this.backgroundClass];
|
||||||
|
|
||||||
|
if (this.debugMode) {
|
||||||
|
classes.push('debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.centerAvatar) {
|
||||||
|
classes.push('centered-avatar');
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes.join(' ');
|
||||||
|
},
|
||||||
backgroundClass () {
|
backgroundClass () {
|
||||||
if (this.member) {
|
if (this.member) {
|
||||||
const { background } = this.member.preferences;
|
const { background } = this.member.preferences;
|
||||||
@@ -256,11 +321,10 @@ export default {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
petClass () {
|
petClass () {
|
||||||
if (some(
|
const foolEvent = this.currentEventList?.find(event => moment()
|
||||||
this.currentEventList,
|
.isBetween(event.start, event.end) && event.aprilFools);
|
||||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'Fungi',
|
if (foolEvent) {
|
||||||
)) {
|
return this.foolPet(this.member.items.currentPet, foolEvent.aprilFools);
|
||||||
return this.foolPet(this.member.items.currentPet);
|
|
||||||
}
|
}
|
||||||
if (this.member?.items.currentPet) return `Pet-${this.member.items.currentPet}`;
|
if (this.member?.items.currentPet) return `Pet-${this.member.items.currentPet}`;
|
||||||
return '';
|
return '';
|
||||||
@@ -290,6 +354,10 @@ export default {
|
|||||||
},
|
},
|
||||||
hideGear (gearType) {
|
hideGear (gearType) {
|
||||||
if (!this.member) return true;
|
if (!this.member) return true;
|
||||||
|
if (!this.showWeapon) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (gearType === 'weapon') {
|
if (gearType === 'weapon') {
|
||||||
const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];
|
const equippedWeapon = this.member.items.gear[this.costumeClass][gearType];
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="option in items"
|
v-for="option in items"
|
||||||
:key="option.key"
|
|
||||||
:id="option.imageName"
|
:id="option.imageName"
|
||||||
|
:key="option.key"
|
||||||
class="outer-option-background"
|
class="outer-option-background"
|
||||||
:class="{
|
:class="{
|
||||||
premium: Boolean(option.gem),
|
premium: Boolean(option.gem),
|
||||||
@@ -28,15 +28,14 @@
|
|||||||
v-if="!option.none"
|
v-if="!option.none"
|
||||||
class="sprite"
|
class="sprite"
|
||||||
:prefix="option.isGear ? 'shop' : 'icon'"
|
:prefix="option.isGear ? 'shop' : 'icon'"
|
||||||
:imageName="option.imageName"
|
|
||||||
:image-name="option.imageName"
|
:image-name="option.imageName"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="redline-outer"
|
class="redline-outer"
|
||||||
>
|
>
|
||||||
<div class="redline"></div>
|
<div class="redline"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,352 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
v-if="isUserMentioned"
|
|
||||||
class="mentioned-icon"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
v-if="hasPermission(user, 'moderator') && msg.flagCount"
|
|
||||||
class="message-hidden"
|
|
||||||
>
|
|
||||||
{{ flagCountDescription }}
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<user-link
|
|
||||||
:user-id="msg.uuid"
|
|
||||||
:name="msg.user"
|
|
||||||
:backer="msg.backer"
|
|
||||||
:contributor="msg.contributor"
|
|
||||||
/>
|
|
||||||
<p class="time">
|
|
||||||
<span
|
|
||||||
v-if="msg.username"
|
|
||||||
class="mr-1"
|
|
||||||
>@{{ msg.username }}</span>
|
|
||||||
<span
|
|
||||||
v-if="msg.username"
|
|
||||||
class="mr-1"
|
|
||||||
>•</span>
|
|
||||||
<span
|
|
||||||
v-b-tooltip.hover="messageDate"
|
|
||||||
>{{ msg.timestamp | timeAgo }} </span>
|
|
||||||
<span v-if="msg.client && user.contributor.level >= 4">({{ msg.client }})</span>
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
ref="markdownContainer"
|
|
||||||
class="text markdown"
|
|
||||||
dir="auto"
|
|
||||||
v-html="parseMarkdown(msg.text)"
|
|
||||||
></div>
|
|
||||||
<hr>
|
|
||||||
<div
|
|
||||||
v-if="msg.id"
|
|
||||||
class="d-flex"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="action d-flex align-items-center"
|
|
||||||
@click="copyAsTodo(msg)"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="svg-icon"
|
|
||||||
v-html="icons.copy"
|
|
||||||
></div>
|
|
||||||
<div>{{ $t('copyAsTodo') }}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="(user.flags.communityGuidelinesAccepted && msg.uuid !== 'system')
|
|
||||||
&& (!isMessageReported || hasPermission(user, 'moderator'))"
|
|
||||||
class="action d-flex align-items-center"
|
|
||||||
@click="report(msg)"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-once
|
|
||||||
class="svg-icon"
|
|
||||||
v-html="icons.report"
|
|
||||||
></div>
|
|
||||||
<div v-once>
|
|
||||||
{{ $t('report') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="msg.uuid === user._id || hasPermission(user, 'moderator')"
|
|
||||||
class="action d-flex align-items-center"
|
|
||||||
@click="remove()"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-once
|
|
||||||
class="svg-icon"
|
|
||||||
v-html="icons.delete"
|
|
||||||
></div>
|
|
||||||
<div v-once>
|
|
||||||
{{ $t('delete') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-b-tooltip="{title: likeTooltip(msg.likes[user._id])}"
|
|
||||||
class="ml-auto d-flex"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="likeCount > 0"
|
|
||||||
class="action d-flex align-items-center mr-0"
|
|
||||||
:class="{activeLike: msg.likes[user._id]}"
|
|
||||||
@click="like()"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="svg-icon"
|
|
||||||
:title="$t('liked')"
|
|
||||||
v-html="icons.liked"
|
|
||||||
></div>
|
|
||||||
+{{ likeCount }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="likeCount === 0"
|
|
||||||
class="action d-flex align-items-center mr-0"
|
|
||||||
:class="{activeLike: msg.likes[user._id]}"
|
|
||||||
@click="like()"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="svg-icon"
|
|
||||||
:title="$t('like')"
|
|
||||||
v-html="icons.like"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span v-if="!msg.likes[user._id]">{{ $t('like') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.at-highlight {
|
|
||||||
background-color: rgba(213, 200, 255, 0.32);
|
|
||||||
padding: 0.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.at-text {
|
|
||||||
color: #6133b4;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '~@/assets/scss/colors.scss';
|
|
||||||
|
|
||||||
.mentioned-icon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #bda8ff;
|
|
||||||
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
|
|
||||||
position: absolute;
|
|
||||||
right: -.5em;
|
|
||||||
top: -.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-hidden {
|
|
||||||
margin-left: 1.5em;
|
|
||||||
margin-top: 1em;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
|
|
||||||
|
|
||||||
.time {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #878190;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #4e4a57;
|
|
||||||
text-align: initial;
|
|
||||||
min-height: 0rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
display: inline-block;
|
|
||||||
color: #878190;
|
|
||||||
margin-right: 1em;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
color: #A5A1AC;
|
|
||||||
margin-right: .2em;
|
|
||||||
width: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.activeLike {
|
|
||||||
color: $purple-300;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
color: $purple-400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import moment from 'moment';
|
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
|
||||||
import escapeRegExp from 'lodash/escapeRegExp';
|
|
||||||
|
|
||||||
import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants';
|
|
||||||
import renderWithMentions from '@/libs/renderWithMentions';
|
|
||||||
import { userStateMixin } from '../../mixins/userState';
|
|
||||||
import userLink from '../userLink';
|
|
||||||
|
|
||||||
import deleteIcon from '@/assets/svg/delete.svg';
|
|
||||||
import copyIcon from '@/assets/svg/copy.svg';
|
|
||||||
import likeIcon from '@/assets/svg/like.svg';
|
|
||||||
import likedIcon from '@/assets/svg/liked.svg';
|
|
||||||
import reportIcon from '@/assets/svg/report.svg';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { userLink },
|
|
||||||
filters: {
|
|
||||||
timeAgo (value) {
|
|
||||||
return moment(value).fromNow();
|
|
||||||
},
|
|
||||||
date (value) {
|
|
||||||
// @TODO: Vue doesn't support this so we cant user preference
|
|
||||||
return moment(value).toDate().toString();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mixins: [userStateMixin],
|
|
||||||
props: {
|
|
||||||
msg: {},
|
|
||||||
groupId: {},
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
icons: Object.freeze({
|
|
||||||
like: likeIcon,
|
|
||||||
copy: copyIcon,
|
|
||||||
report: reportIcon,
|
|
||||||
delete: deleteIcon,
|
|
||||||
liked: likedIcon,
|
|
||||||
}),
|
|
||||||
reported: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isUserMentioned () {
|
|
||||||
const message = this.msg;
|
|
||||||
|
|
||||||
if (message.highlight) return true;
|
|
||||||
|
|
||||||
const { user } = this;
|
|
||||||
const displayName = user.profile.name;
|
|
||||||
const { username } = user.auth.local;
|
|
||||||
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
|
|
||||||
message.highlight = new RegExp(pattern, 'i').test(message.text);
|
|
||||||
|
|
||||||
return message.highlight;
|
|
||||||
},
|
|
||||||
likeCount () {
|
|
||||||
const message = this.msg;
|
|
||||||
if (!message.likes) return 0;
|
|
||||||
|
|
||||||
let likeCount = 0;
|
|
||||||
for (const key of Object.keys(message.likes)) {
|
|
||||||
const like = message.likes[key];
|
|
||||||
if (like) likeCount += 1;
|
|
||||||
}
|
|
||||||
return likeCount;
|
|
||||||
},
|
|
||||||
isMessageReported () {
|
|
||||||
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
|
|
||||||
},
|
|
||||||
flagCountDescription () {
|
|
||||||
if (!this.msg.flagCount) return '';
|
|
||||||
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) return 'Message flagged once, not hidden';
|
|
||||||
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) return 'Message hidden';
|
|
||||||
return 'Message hidden (shadow-muted)';
|
|
||||||
},
|
|
||||||
messageDate () {
|
|
||||||
const date = moment(this.msg.timestamp).toDate();
|
|
||||||
return date.toString();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
const links = this.$refs.markdownContainer.getElementsByTagName('a');
|
|
||||||
for (let i = 0; i < links.length; i += 1) {
|
|
||||||
let link = links[i].pathname;
|
|
||||||
|
|
||||||
// Internet Explorer does not provide the leading slash character in the pathname
|
|
||||||
link = link.charAt(0) === '/' ? link : `/${link}`;
|
|
||||||
|
|
||||||
if (link.startsWith('/profile/')) {
|
|
||||||
links[i].onclick = ev => {
|
|
||||||
ev.preventDefault();
|
|
||||||
this.$router.push({ path: link });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.CHAT_FLAG_LIMIT_FOR_HIDING = CHAT_FLAG_LIMIT_FOR_HIDING;
|
|
||||||
this.CHAT_FLAG_FROM_SHADOW_MUTE = CHAT_FLAG_FROM_SHADOW_MUTE;
|
|
||||||
this.$emit('chat-card-mounted', this.msg.id);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async like () {
|
|
||||||
const message = cloneDeep(this.msg);
|
|
||||||
|
|
||||||
await this.$store.dispatch('chat:like', {
|
|
||||||
groupId: this.groupId,
|
|
||||||
chatId: message.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
message.likes[this.user._id] = !message.likes[this.user._id];
|
|
||||||
|
|
||||||
this.$emit('message-liked', message);
|
|
||||||
this.$root.$emit('bv::hide::tooltip');
|
|
||||||
},
|
|
||||||
likeTooltip (likedStatus) {
|
|
||||||
if (!likedStatus) return this.$t('like');
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
copyAsTodo (message) {
|
|
||||||
this.$root.$emit('habitica::copy-as-todo', message);
|
|
||||||
},
|
|
||||||
report () {
|
|
||||||
this.$root.$on('habitica:report-result', data => {
|
|
||||||
if (data.ok) {
|
|
||||||
this.reported = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$root.$off('habitica:report-result');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$root.$emit('habitica::report-chat', {
|
|
||||||
message: this.msg,
|
|
||||||
groupId: this.groupId || 'privateMessage',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async remove () {
|
|
||||||
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
|
|
||||||
|
|
||||||
const message = this.msg;
|
|
||||||
this.$emit('message-removed', message);
|
|
||||||
|
|
||||||
await this.$store.dispatch('chat:deleteChat', {
|
|
||||||
groupId: this.groupId,
|
|
||||||
chatId: message.id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
parseMarkdown (text) {
|
|
||||||
return renderWithMentions(text, this.user);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -3,15 +3,6 @@
|
|||||||
ref="container"
|
ref="container"
|
||||||
class="container-fluid"
|
class="container-fluid"
|
||||||
>
|
>
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<copy-as-todo-modal
|
|
||||||
:group-type="groupType"
|
|
||||||
:group-name="groupName"
|
|
||||||
:group-id="groupId"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row loadmore">
|
<div class="row loadmore">
|
||||||
<div v-if="canLoadMore">
|
<div v-if="canLoadMore">
|
||||||
<div class="loadmore-divider"></div>
|
<div class="loadmore-divider"></div>
|
||||||
@@ -33,11 +24,14 @@
|
|||||||
<div
|
<div
|
||||||
v-for="msg in messages.filter(m => chat && canViewFlag(m))"
|
v-for="msg in messages.filter(m => chat && canViewFlag(m))"
|
||||||
:key="msg.id"
|
:key="msg.id"
|
||||||
|
class="message-row"
|
||||||
|
:class="{ 'margin-right': user._id !== msg.uuid}"
|
||||||
>
|
>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<avatar
|
<avatar
|
||||||
v-if="user._id !== msg.uuid && msg.uuid !== 'system'"
|
v-if="user._id !== msg.uuid && msg.uuid !== 'system'"
|
||||||
class="avatar-left"
|
class="avatar-left"
|
||||||
|
:height="null"
|
||||||
:class="{ invisible: avatarUnavailable(msg) }"
|
:class="{ invisible: avatarUnavailable(msg) }"
|
||||||
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
|
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
|
||||||
:avatar-only="true"
|
:avatar-only="true"
|
||||||
@@ -45,20 +39,19 @@
|
|||||||
:override-top-padding="'14px'"
|
:override-top-padding="'14px'"
|
||||||
@click.native="showMemberModal(msg.uuid)"
|
@click.native="showMemberModal(msg.uuid)"
|
||||||
/>
|
/>
|
||||||
<div class="card">
|
<message-card
|
||||||
<chat-card
|
:msg="msg"
|
||||||
:msg="msg"
|
:group-id="groupId"
|
||||||
:group-id="groupId"
|
:user-sent-message="user._id === msg.uuid"
|
||||||
@message-liked="messageLiked"
|
@message-liked="messageLiked"
|
||||||
@message-removed="messageRemoved"
|
@message-removed="messageRemoved"
|
||||||
@show-member-modal="showMemberModal"
|
@message-card-mounted="itemWasMounted"
|
||||||
@chat-card-mounted="itemWasMounted"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<avatar
|
<avatar
|
||||||
v-if="user._id === msg.uuid"
|
v-if="user._id === msg.uuid"
|
||||||
:class="{ invisible: avatarUnavailable(msg) }"
|
:class="{ invisible: avatarUnavailable(msg) }"
|
||||||
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
|
:member="msg.userStyles || cachedProfileData[msg.uuid] || {}"
|
||||||
|
:height="null"
|
||||||
:avatar-only="true"
|
:avatar-only="true"
|
||||||
:hide-class-badge="true"
|
:hide-class-badge="true"
|
||||||
:override-top-padding="'14px'"
|
:override-top-padding="'14px'"
|
||||||
@@ -105,11 +98,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-left {
|
|
||||||
margin-left: -1.5rem;
|
|
||||||
margin-right: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hr {
|
.hr {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@@ -137,11 +125,27 @@
|
|||||||
margin-bottom: .5em;
|
margin-bottom: .5em;
|
||||||
padding: 0rem;
|
padding: 0rem;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|
||||||
|
&.system-message {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-scroll .d-flex {
|
.message-scroll .d-flex {
|
||||||
min-width: 1px;
|
min-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-row {
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
|
||||||
|
&:not(.margin-right) {
|
||||||
|
.d-flex {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -152,13 +156,13 @@ import findIndex from 'lodash/findIndex';
|
|||||||
import { userStateMixin } from '../../mixins/userState';
|
import { userStateMixin } from '../../mixins/userState';
|
||||||
|
|
||||||
import Avatar from '../avatar';
|
import Avatar from '../avatar';
|
||||||
import copyAsTodoModal from './copyAsTodoModal';
|
import MessageCard from '@/components/messages/messageCard.vue';
|
||||||
import chatCard from './chatCard';
|
|
||||||
|
// TODO merge chatMessages.vue (party message list) with messageList.vue (private message list)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
copyAsTodoModal,
|
MessageCard,
|
||||||
chatCard,
|
|
||||||
Avatar,
|
Avatar,
|
||||||
},
|
},
|
||||||
mixins: [userStateMixin],
|
mixins: [userStateMixin],
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
<template>
|
|
||||||
<b-modal
|
|
||||||
id="copyAsTodo"
|
|
||||||
:title="$t('copyMessageAsToDo')"
|
|
||||||
:hide-footer="true"
|
|
||||||
size="md"
|
|
||||||
>
|
|
||||||
<div class="form-group">
|
|
||||||
<input
|
|
||||||
v-model="task.text"
|
|
||||||
class="form-control"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<textarea
|
|
||||||
v-model="task.notes"
|
|
||||||
class="form-control"
|
|
||||||
rows="5"
|
|
||||||
focus-element="true"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<task
|
|
||||||
v-if="task._id"
|
|
||||||
:is-user="isUser"
|
|
||||||
:task="task"
|
|
||||||
/>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
|
||||||
class="btn btn-secondary"
|
|
||||||
@click="close()"
|
|
||||||
>
|
|
||||||
{{ $t('close') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="saveTodo()"
|
|
||||||
>
|
|
||||||
{{ $t('submit') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</b-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import taskDefaults from '@/../../common/script/libs/taskDefaults';
|
|
||||||
import { mapActions } from '@/libs/store';
|
|
||||||
import markdownDirective from '@/directives/markdown';
|
|
||||||
import notificationsMixin from '@/mixins/notifications';
|
|
||||||
import Task from '@/components/tasks/task';
|
|
||||||
|
|
||||||
const baseUrl = 'https://habitica.com';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
directives: {
|
|
||||||
markdown: markdownDirective,
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Task,
|
|
||||||
},
|
|
||||||
mixins: [notificationsMixin],
|
|
||||||
props: ['copyingMessage', 'groupType', 'groupName', 'groupId'],
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
isUser: true,
|
|
||||||
task: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
this.$root.$on('habitica::copy-as-todo', message => {
|
|
||||||
const notes = `${message.user || 'system message'}${message.user ? ' wrote' : ''} in [${this.groupName}](${this.groupPath()})`;
|
|
||||||
const newTask = {
|
|
||||||
text: message.text,
|
|
||||||
type: 'todo',
|
|
||||||
notes,
|
|
||||||
};
|
|
||||||
this.task = taskDefaults(newTask, this.$store.state.user.data);
|
|
||||||
this.$root.$emit('bv::show::modal', 'copyAsTodo');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
beforeDestroy () {
|
|
||||||
this.$root.$off('habitica::copy-as-todo');
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions({
|
|
||||||
createTask: 'tasks:create',
|
|
||||||
}),
|
|
||||||
groupPath () {
|
|
||||||
if (this.groupType === 'party') {
|
|
||||||
return `${baseUrl}/party`;
|
|
||||||
}
|
|
||||||
return `${baseUrl}/groups/guild/${this.groupId}`;
|
|
||||||
},
|
|
||||||
close () {
|
|
||||||
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
|
|
||||||
},
|
|
||||||
saveTodo () {
|
|
||||||
this.createTask(this.task);
|
|
||||||
this.text(this.$t('messageAddedAsToDo'));
|
|
||||||
this.$root.$emit('bv::hide::modal', 'copyAsTodo');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:class="{'user-entry': newMessage}"
|
:class="{'user-entry': newMessage}"
|
||||||
:maxlength="MAX_MESSAGE_LENGTH"
|
:maxlength="MAX_MESSAGE_LENGTH"
|
||||||
@keydown="updateCarretPosition"
|
@keydown="autoCompleteMixinUpdateCarretPosition"
|
||||||
@keyup.ctrl.enter="sendMessageShortcut()"
|
@keyup.ctrl.enter="sendMessageShortcut()"
|
||||||
@keydown.tab="handleTab($event)"
|
@keydown.tab="autoCompleteMixinHandleTab($event)"
|
||||||
@keydown.up="selectPreviousAutocomplete($event)"
|
@keydown.up="autoCompleteMixinSelectPreviousAutocomplete($event)"
|
||||||
@keydown.down="selectNextAutocomplete($event)"
|
@keydown.down="autoCompleteMixinSelectNextAutocomplete($event)"
|
||||||
@keypress.enter="selectAutocomplete($event)"
|
@keypress.enter="autoCompleteMixinSelectAutocomplete($event)"
|
||||||
@keydown.esc="handleEscape($event)"
|
@keydown.esc="autoCompleteMixinHandleEscape($event)"
|
||||||
@paste="disableMessageSendShortcut()"
|
@paste="disableMessageSendShortcut()"
|
||||||
></textarea>
|
></textarea>
|
||||||
<span>{{ currentLength }} / {{ MAX_MESSAGE_LENGTH }}</span>
|
<span>{{ currentLength }} / {{ MAX_MESSAGE_LENGTH }}</span>
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
ref="autocomplete"
|
ref="autocomplete"
|
||||||
:text="newMessage"
|
:text="newMessage"
|
||||||
:textbox="textbox"
|
:textbox="textbox"
|
||||||
:coords="coords"
|
:coords="mixinData.autoComplete.coords"
|
||||||
:caret-position="caretPosition"
|
:caret-position="mixinData.autoComplete.caretPosition"
|
||||||
:chat="group.chat"
|
:chat="group.chat"
|
||||||
@select="selectedAutocomplete"
|
@select="selectedAutocomplete"
|
||||||
/>
|
/>
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
<slot name="additionRow"></slot>
|
<slot name="additionRow"></slot>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="hr col-12"></div>
|
<div class="hr col-12"></div>
|
||||||
<chat-message
|
<chat-messages
|
||||||
:chat.sync="group.chat"
|
:chat.sync="group.chat"
|
||||||
:group-type="group.type"
|
:group-type="group.type"
|
||||||
:group-id="group._id"
|
:group-id="group._id"
|
||||||
@@ -86,16 +86,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
|
|
||||||
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
|
import { MAX_MESSAGE_LENGTH } from '@/../../common/script/constants';
|
||||||
import externalLinks from '../../mixins/externalLinks';
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
|
|
||||||
import autocomplete from '../chat/autoComplete';
|
import autocomplete from '../chat/autoComplete';
|
||||||
import communityGuidelines from './communityGuidelines';
|
import communityGuidelines from './communityGuidelines';
|
||||||
import chatMessage from '../chat/chatMessages';
|
import chatMessages from '../chat/chatMessages';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import markdownDirective from '@/directives/markdown';
|
import markdownDirective from '@/directives/markdown';
|
||||||
|
import { autoCompleteHelperMixin } from '@/mixins/autoCompleteHelper';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
directives: {
|
directives: {
|
||||||
@@ -104,23 +103,18 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
autocomplete,
|
autocomplete,
|
||||||
communityGuidelines,
|
communityGuidelines,
|
||||||
chatMessage,
|
chatMessages,
|
||||||
},
|
},
|
||||||
mixins: [externalLinks],
|
mixins: [externalLinks, autoCompleteHelperMixin],
|
||||||
props: ['label', 'group', 'placeholder'],
|
props: ['label', 'group', 'placeholder'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
newMessage: '',
|
newMessage: '',
|
||||||
sending: false,
|
sending: false,
|
||||||
caretPosition: 0,
|
|
||||||
chat: {
|
chat: {
|
||||||
submitDisable: false,
|
submitDisable: false,
|
||||||
submitTimeout: null,
|
submitTimeout: null,
|
||||||
},
|
},
|
||||||
coords: {
|
|
||||||
TOP: 0,
|
|
||||||
LEFT: 0,
|
|
||||||
},
|
|
||||||
textbox: null,
|
textbox: null,
|
||||||
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
|
MAX_MESSAGE_LENGTH: MAX_MESSAGE_LENGTH.toString(),
|
||||||
};
|
};
|
||||||
@@ -142,35 +136,6 @@ export default {
|
|||||||
this.handleExternalLinks();
|
this.handleExternalLinks();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
|
||||||
getCoord (e, text) {
|
|
||||||
this.caretPosition = text.selectionEnd;
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const span = document.createElement('span');
|
|
||||||
const copyStyle = getComputedStyle(text);
|
|
||||||
|
|
||||||
[].forEach.call(copyStyle, prop => {
|
|
||||||
div.style[prop] = copyStyle[prop];
|
|
||||||
});
|
|
||||||
|
|
||||||
div.style.position = 'absolute';
|
|
||||||
document.body.appendChild(div);
|
|
||||||
div.textContent = text.value.substr(0, this.caretPosition);
|
|
||||||
span.textContent = text.value.substr(this.caretPosition) || '.';
|
|
||||||
div.appendChild(span);
|
|
||||||
this.coords = {
|
|
||||||
TOP: span.offsetTop,
|
|
||||||
LEFT: span.offsetLeft,
|
|
||||||
};
|
|
||||||
document.body.removeChild(div);
|
|
||||||
},
|
|
||||||
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
|
|
||||||
this._updateCarretPosition(eventUpdate);
|
|
||||||
}, 250),
|
|
||||||
_updateCarretPosition (eventUpdate) {
|
|
||||||
const text = eventUpdate.target;
|
|
||||||
this.getCoord(eventUpdate, text);
|
|
||||||
},
|
|
||||||
async sendMessageShortcut () {
|
async sendMessageShortcut () {
|
||||||
// If the user recently pasted in the text field, don't submit
|
// If the user recently pasted in the text field, don't submit
|
||||||
if (!this.chat.submitDisable) {
|
if (!this.chat.submitDisable) {
|
||||||
@@ -221,50 +186,6 @@ export default {
|
|||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTab (e) {
|
|
||||||
if (this.$refs.autocomplete.searchActive) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (e.shiftKey) {
|
|
||||||
this.$refs.autocomplete.selectPrevious();
|
|
||||||
} else {
|
|
||||||
this.$refs.autocomplete.selectNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEscape (e) {
|
|
||||||
if (this.$refs.autocomplete.searchActive) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.$refs.autocomplete.cancel();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectNextAutocomplete (e) {
|
|
||||||
if (this.$refs.autocomplete.searchActive) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.$refs.autocomplete.selectNext();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectPreviousAutocomplete (e) {
|
|
||||||
if (this.$refs.autocomplete.searchActive) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.$refs.autocomplete.selectPrevious();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectAutocomplete (e) {
|
|
||||||
if (this.$refs.autocomplete.searchActive) {
|
|
||||||
if (this.$refs.autocomplete.selected !== null) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.$refs.autocomplete.makeSelection();
|
|
||||||
} else {
|
|
||||||
// no autocomplete selected, newline instead
|
|
||||||
this.$refs.autocomplete.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectedAutocomplete (newText, newCaret) {
|
selectedAutocomplete (newText, newCaret) {
|
||||||
this.newMessage = newText;
|
this.newMessage = newText;
|
||||||
// Wait for v-modal to update
|
// Wait for v-modal to update
|
||||||
@@ -273,7 +194,6 @@ export default {
|
|||||||
this.textbox.focus();
|
this.textbox.focus();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchRecentMessages () {
|
fetchRecentMessages () {
|
||||||
this.$emit('fetchRecentMessages');
|
this.$emit('fetchRecentMessages');
|
||||||
},
|
},
|
||||||
@@ -284,10 +204,7 @@ export default {
|
|||||||
beforeRouteUpdate (to, from, next) {
|
beforeRouteUpdate (to, from, next) {
|
||||||
// Reset chat
|
// Reset chat
|
||||||
this.newMessage = '';
|
this.newMessage = '';
|
||||||
this.coords = {
|
this.autoCompleteMixinResetCoordsPosition();
|
||||||
TOP: 0,
|
|
||||||
LEFT: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -225,10 +225,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="quest-icon">
|
<div class="quest-icon">
|
||||||
<div
|
<Sprite
|
||||||
class="quest"
|
class="quest"
|
||||||
:class="`inventory_quest_scroll_${questData.key}`"
|
:image-name="`inventory_quest_scroll_${questData.key}`" />
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -297,7 +297,7 @@
|
|||||||
<div class="topbar-dropdown">
|
<div class="topbar-dropdown">
|
||||||
<router-link
|
<router-link
|
||||||
v-if="user.permissions.fullAccess ||
|
v-if="user.permissions.fullAccess ||
|
||||||
user.permissions.userSupport || user.permissions.newsPoster"
|
user.permissions.userSupport"
|
||||||
class="topbar-dropdown-item dropdown-item"
|
class="topbar-dropdown-item dropdown-item"
|
||||||
:to="{name: 'adminPanel'}"
|
:to="{name: 'adminPanel'}"
|
||||||
>
|
>
|
||||||
@@ -334,11 +334,6 @@
|
|||||||
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
href="https://docs.google.com/forms/d/e/1FAIpQLScPhrwq_7P1C6PTrI3lbvTsvqGyTNnGzp1ugi1Ml0PFee_p5g/viewform?usp=sf_link"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>{{ $t('requestFeature') }}</a>
|
>{{ $t('requestFeature') }}</a>
|
||||||
<a
|
|
||||||
class="topbar-dropdown-item dropdown-item"
|
|
||||||
href="https://habitica.fandom.com/wiki/Habitica_Wiki"
|
|
||||||
target="_blank"
|
|
||||||
>{{ $t('wiki') }}</a>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</b-navbar-nav>
|
</b-navbar-nav>
|
||||||
|
|||||||
@@ -12,20 +12,21 @@
|
|||||||
<strong> {{ notification.data.title }} </strong>
|
<strong> {{ notification.data.title }} </strong>
|
||||||
<span> {{ notification.data.text }} </span>
|
<span> {{ notification.data.text }} </span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<Sprite
|
||||||
slot="icon"
|
slot="icon"
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
:class="notification.data.icon"
|
:image-name="notification.data.icon" />
|
||||||
></div>
|
|
||||||
</base-notification>
|
</base-notification>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BaseNotification from './base';
|
import BaseNotification from './base';
|
||||||
|
import Sprite from '@/components/ui/sprite.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BaseNotification,
|
BaseNotification,
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
notification: {
|
notification: {
|
||||||
@@ -41,7 +42,8 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
action () {
|
action () {
|
||||||
if (!this.notification || !this.notification.data) {
|
if (!this.notification || !this.notification.data
|
||||||
|
|| this.notification.data.destination === this.$route.path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.notification.data.destination.indexOf('backgrounds') !== -1) {
|
if (this.notification.data.destination.indexOf('backgrounds') !== -1) {
|
||||||
|
|||||||
@@ -10,20 +10,21 @@
|
|||||||
slot="content"
|
slot="content"
|
||||||
v-html="$t('newSubscriberItem')"
|
v-html="$t('newSubscriberItem')"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<Sprite
|
||||||
slot="icon"
|
slot="icon"
|
||||||
:class="mysteryClass"
|
:image-name="mysteryClass" />
|
||||||
></div>
|
|
||||||
</base-notification>
|
</base-notification>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import BaseNotification from './base';
|
import BaseNotification from './base';
|
||||||
|
import Sprite from '@/components/ui/sprite.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BaseNotification,
|
BaseNotification,
|
||||||
|
Sprite,
|
||||||
},
|
},
|
||||||
props: ['notification', 'canRemove'],
|
props: ['notification', 'canRemove'],
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div slot="drawer-header">
|
<div slot="drawer-header">
|
||||||
<div class="drawer-tab-container">
|
<div class="drawer-tab-container">
|
||||||
<div class="clearfix">
|
<div class="clearfix mb-2">
|
||||||
<toggle-switch
|
<toggle-switch
|
||||||
class="float-right align-with-tab"
|
class="float-right align-with-tab"
|
||||||
:label="$t(costumeMode ? 'useCostume' : 'autoEquipBattleGear')"
|
:label="$t(costumeMode ? 'useCostume' : 'autoEquipBattleGear')"
|
||||||
@@ -410,7 +410,8 @@ export default {
|
|||||||
const ownedItem = this.flatGear[gearKey];
|
const ownedItem = this.flatGear[gearKey];
|
||||||
|
|
||||||
const isSearched = !searchText
|
const isSearched = !searchText
|
||||||
|| ownedItem.text().toLowerCase().indexOf(searchText) !== -1;
|
|| ownedItem.text().toLowerCase().indexOf(searchText) !== -1
|
||||||
|
|| ownedItem.notes().toLowerCase().indexOf(searchText) !== -1;
|
||||||
|
|
||||||
if (ownedItem.klass !== 'base' && isSearched) {
|
if (ownedItem.klass !== 'base' && isSearched) {
|
||||||
const { type } = ownedItem;
|
const { type } = ownedItem;
|
||||||
|
|||||||
@@ -338,9 +338,10 @@ export default {
|
|||||||
if (itemQuantity > 0 && isAllowed) {
|
if (itemQuantity > 0 && isAllowed) {
|
||||||
const item = contentItems[itemKey];
|
const item = contentItems[itemKey];
|
||||||
|
|
||||||
const isSearched = !searchText || item.text()
|
const isSearched = !searchText
|
||||||
.toLowerCase()
|
|| item.text().toLowerCase().indexOf(searchText) !== -1
|
||||||
.indexOf(searchText) !== -1;
|
|| item.notes().toLowerCase().indexOf(searchText) !== -1;
|
||||||
|
|
||||||
if (isSearched && item) {
|
if (isSearched && item) {
|
||||||
itemsArray.push({
|
itemsArray.push({
|
||||||
...item,
|
...item,
|
||||||
|
|||||||
@@ -627,6 +627,8 @@ export default {
|
|||||||
animals.push({
|
animals.push({
|
||||||
key: specialKey,
|
key: specialKey,
|
||||||
eggKey,
|
eggKey,
|
||||||
|
eggName: text(),
|
||||||
|
mountName: text(),
|
||||||
potionKey,
|
potionKey,
|
||||||
name: text(),
|
name: text(),
|
||||||
canFind,
|
canFind,
|
||||||
@@ -657,7 +659,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.cachedAnimalList[key] = animals;
|
this.cachedAnimalList[key] = animals;
|
||||||
|
|
||||||
return animals;
|
return animals;
|
||||||
},
|
},
|
||||||
listAnimals (animalGroup, type, hideMissing, sort, searchText) {
|
listAnimals (animalGroup, type, hideMissing, sort, searchText) {
|
||||||
@@ -670,7 +671,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (searchText && searchText !== '') {
|
if (searchText && searchText !== '') {
|
||||||
animals = _filter(animals, a => a.name.toLowerCase().indexOf(searchText) !== -1);
|
animals = _filter(animals, a => a.name.toLowerCase().indexOf(searchText) !== -1
|
||||||
|
|| a.eggName.toLowerCase().indexOf(searchText) !== -1
|
||||||
|
|| a.mountName.toLowerCase().indexOf(searchText) !== -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Sort
|
// 2. Sort
|
||||||
@@ -693,7 +696,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.viewOptions[animalGroup.key].animalCount = animals.length;
|
this.viewOptions[animalGroup.key].animalCount = animals.length;
|
||||||
|
|
||||||
return animals;
|
return animals;
|
||||||
},
|
},
|
||||||
countOwnedAnimals (animalGroup, type) {
|
countOwnedAnimals (animalGroup, type) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="pet-background">
|
<div class="pet-background">
|
||||||
<Sprite
|
<Sprite
|
||||||
class="mount"
|
class="mount"
|
||||||
:image-name="`Mount_Icon_${mount.key}`"
|
:image-name="`stable_Mount_Icon_${mount.key}`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="title">
|
<h4 class="title">
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
|
|
||||||
.mount {
|
.mount {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
margin-top: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
|
|||||||
@@ -114,7 +114,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import some from 'lodash/some';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
@@ -183,13 +182,12 @@ export default {
|
|||||||
return 'GreyedOut';
|
return 'GreyedOut';
|
||||||
},
|
},
|
||||||
imageName () {
|
imageName () {
|
||||||
if (this.isOwned() && some(
|
const foolEvent = this.currentEventList?.find(event => moment()
|
||||||
this.currentEventList,
|
.isBetween(event.start, event.end) && event.aprilFools);
|
||||||
event => moment().isBetween(event.start, event.end) && event.aprilFools && event.aprilFools === 'Fungi',
|
if (this.isOwned() && foolEvent) {
|
||||||
)) {
|
if (this.isSpecial()) return `stable_${this.foolPet(this.item.key, foolEvent.aprilFools)}`;
|
||||||
if (this.isSpecial()) return `stable_${this.foolPet(this.item.key)}`;
|
|
||||||
const petString = `${this.item.eggKey}-${this.item.key}`;
|
const petString = `${this.item.eggKey}-${this.item.key}`;
|
||||||
return `stable_${this.foolPet(petString)}`;
|
return `stable_${this.foolPet(petString, foolEvent.aprilFools)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isOwned() || (this.mountOwned() && this.isHatchable())) {
|
if (this.isOwned() || (this.mountOwned() && this.isHatchable())) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
:name="member.profile.name"
|
:name="member.profile.name"
|
||||||
:backer="member.backer"
|
:backer="member.backer"
|
||||||
:contributor="member.contributor"
|
:contributor="member.contributor"
|
||||||
:smaller-style="true"
|
|
||||||
/>
|
/>
|
||||||
<inline-class-badge
|
<inline-class-badge
|
||||||
v-if="member.stats"
|
v-if="member.stats"
|
||||||
|
|||||||
114
website/client/src/components/messages/likeButton.vue
Normal file
114
website/client/src/components/messages/likeButton.vue
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="d-inline-flex like-button"
|
||||||
|
@click="like()"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-b-tooltip="{title: likeTooltip(likeCount)}"
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="likeCount > 0"
|
||||||
|
class="action d-flex align-items-center mr-0"
|
||||||
|
:class="{isLiked: true, currentUserLiked: likedByCurrentUser}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="svg-icon mr-1"
|
||||||
|
:title="$t('liked')"
|
||||||
|
v-html="likedIcon"
|
||||||
|
></div>
|
||||||
|
+{{ likeCount }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="likeCount === 0"
|
||||||
|
class="action d-flex align-items-center mr-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="svg-icon"
|
||||||
|
:title="$t('like')"
|
||||||
|
v-html="icons.like"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span v-if="likeCount === 0">{{ $t('like') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
@import '~@/assets/scss/tiers.scss';
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 1em;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
color: $gray-100;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.isLiked.currentUserLiked {
|
||||||
|
color: $purple-200;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
color: $purple-300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.like-button {
|
||||||
|
color: $gray-100;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: $purple-200;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
color: $purple-300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import likeIcon from '@/assets/svg/like.svg';
|
||||||
|
import likedIcon from '@/assets/svg/liked.svg';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
likeCount: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
likedByCurrentUser: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
icons: Object.freeze({
|
||||||
|
like: likeIcon,
|
||||||
|
liked: likedIcon,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
likedIcon () {
|
||||||
|
return this.likedByCurrentUser ? this.icons.liked : this.icons.like;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async like () {
|
||||||
|
this.$emit('toggle-like');
|
||||||
|
},
|
||||||
|
likeTooltip (likedStatus) {
|
||||||
|
if (!likedStatus) return this.$t('like');
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,73 +1,157 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card-body">
|
<div
|
||||||
<user-link
|
class="card"
|
||||||
:user-id="msg.uuid"
|
:class="{
|
||||||
:name="msg.user"
|
'system-message': isSystemMessage
|
||||||
:backer="msg.backer"
|
}"
|
||||||
:contributor="msg.contributor"
|
>
|
||||||
/>
|
|
||||||
<p class="time">
|
|
||||||
<span
|
|
||||||
v-if="msg.username"
|
|
||||||
class="mr-1"
|
|
||||||
>@{{ msg.username }}</span><span
|
|
||||||
v-if="msg.username"
|
|
||||||
class="mr-1"
|
|
||||||
>•</span>
|
|
||||||
<span
|
|
||||||
v-b-tooltip.hover="messageDate"
|
|
||||||
>{{ msg.timestamp | timeAgo }} </span>
|
|
||||||
<span v-if="msg.client && user.contributor.level >= 4"> ({{ msg.client }})</span>
|
|
||||||
</p>
|
|
||||||
<div
|
<div
|
||||||
class="text markdown"
|
v-b-tooltip.hover="messageDateForSystemMessage"
|
||||||
dir="auto"
|
class="message-card"
|
||||||
v-html="parseMarkdown(msg.text)"
|
|
||||||
></div>
|
:class="{
|
||||||
<div
|
'user-sent-message': userSentMessage,
|
||||||
v-if="isMessageReported"
|
'user-received-message': !userSentMessage && !isSystemMessage,
|
||||||
class="reported"
|
'system-message': isSystemMessage
|
||||||
>
|
}"
|
||||||
<span v-once>{{ $t('reportedMessage') }}</span><br>
|
|
||||||
<span v-once>{{ $t('canDeleteNow') }}</span>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div
|
|
||||||
v-if="msg.id"
|
|
||||||
class="d-flex"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="!isMessageReported"
|
v-if="isUserMentioned"
|
||||||
class="action d-flex align-items-center"
|
class="mentioned-icon"
|
||||||
@click="report(msg)"
|
></div>
|
||||||
|
<div
|
||||||
|
v-if="userIsModerator && msg.flagCount"
|
||||||
|
class="message-hidden"
|
||||||
>
|
>
|
||||||
<div
|
{{ flagCountDescription }}
|
||||||
v-once
|
|
||||||
class="svg-icon"
|
|
||||||
v-html="icons.report"
|
|
||||||
></div>
|
|
||||||
<div v-once>
|
|
||||||
{{ $t('report') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="action d-flex align-items-center"
|
class="card-body"
|
||||||
@click="remove()"
|
|
||||||
>
|
>
|
||||||
|
<user-link
|
||||||
|
v-if="!isSystemMessage"
|
||||||
|
:user-id="msg.uuid"
|
||||||
|
:name="msg.user"
|
||||||
|
:backer="msg.backer"
|
||||||
|
:contributor="msg.contributor"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
v-if="!isSystemMessage"
|
||||||
|
class="time"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="msg.username"
|
||||||
|
class="mr-1"
|
||||||
|
>@{{ msg.username }}</span><span
|
||||||
|
v-if="msg.username"
|
||||||
|
class="mr-1"
|
||||||
|
>•</span>
|
||||||
|
<span v-b-tooltip.hover="messageDate">{{ msg.timestamp | timeAgo }} </span>
|
||||||
|
<span v-if="msg.client && user.contributor.level >= 4">
|
||||||
|
({{ msg.client }})
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<b-dropdown
|
||||||
|
v-if="!isSystemMessage"
|
||||||
|
right="right"
|
||||||
|
variant="flat"
|
||||||
|
toggle-class="with-icon"
|
||||||
|
class="card-menu no-min-width"
|
||||||
|
:no-caret="true"
|
||||||
|
>
|
||||||
|
<template #button-content>
|
||||||
|
<span
|
||||||
|
v-once
|
||||||
|
class="svg-icon inline menuIcon color"
|
||||||
|
v-html="icons.menuIcon"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<b-dropdown-item
|
||||||
|
class="selectListItem"
|
||||||
|
@click="copy(msg)"
|
||||||
|
>
|
||||||
|
<span class="with-icon">
|
||||||
|
<span
|
||||||
|
v-once
|
||||||
|
class="svg-icon icon-16 color"
|
||||||
|
v-html="icons.copy"
|
||||||
|
></span>
|
||||||
|
<span v-once>
|
||||||
|
{{ $t('copy') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</b-dropdown-item>
|
||||||
|
<b-dropdown-item
|
||||||
|
v-if="canReportMessage"
|
||||||
|
class="selectListItem custom-hover--red"
|
||||||
|
@click="report(msg)"
|
||||||
|
>
|
||||||
|
<span class="with-icon">
|
||||||
|
<span
|
||||||
|
v-once
|
||||||
|
class="svg-icon icon-16 color"
|
||||||
|
v-html="icons.report"
|
||||||
|
></span>
|
||||||
|
<span v-once>
|
||||||
|
{{ $t('report') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</b-dropdown-item>
|
||||||
|
<b-dropdown-item
|
||||||
|
v-if="canDeleteMessage"
|
||||||
|
class="selectListItem custom-hover--red"
|
||||||
|
@click="remove()"
|
||||||
|
>
|
||||||
|
<span class="with-icon">
|
||||||
|
<span
|
||||||
|
v-once
|
||||||
|
class="svg-icon icon-16 color"
|
||||||
|
v-html="icons.delete"
|
||||||
|
></span>
|
||||||
|
<span v-once>
|
||||||
|
{{ $t('delete') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</b-dropdown-item>
|
||||||
|
</b-dropdown>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-once
|
v-if="isSystemMessage"
|
||||||
class="svg-icon"
|
class="system-message-body"
|
||||||
v-html="icons.delete"
|
>
|
||||||
></div>
|
{{ msg.unformattedText }}
|
||||||
<div v-once>
|
|
||||||
{{ $t('delete') }}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
ref="markdownContainer"
|
||||||
|
class="text markdown"
|
||||||
|
dir="auto"
|
||||||
|
v-html="parseMarkdown(msg.text)"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-if="isMessageReported"
|
||||||
|
class="reported"
|
||||||
|
>
|
||||||
|
<span v-once>{{ $t('reportedMessage') }}</span><br>
|
||||||
|
<span v-once>{{ $t('canDeleteNow') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<like-button
|
||||||
|
v-if="canLikeMessage"
|
||||||
|
class="mt-75"
|
||||||
|
:liked-by-current-user="msg.likes[user._id]"
|
||||||
|
:like-count="likeCount"
|
||||||
|
@toggle-like="like()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.message-card {
|
||||||
.at-highlight {
|
.at-highlight {
|
||||||
background-color: rgba(213, 200, 255, 0.32);
|
background-color: rgba(213, 200, 255, 0.32);
|
||||||
padding: 0.1rem;
|
padding: 0.1rem;
|
||||||
@@ -76,43 +160,76 @@
|
|||||||
.at-text {
|
.at-text {
|
||||||
color: #6133b4;
|
color: #6133b4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-menu button {
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown p:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
@import '~@/assets/scss/tiers.scss';
|
@import '~@/assets/scss/tiers.scss';
|
||||||
|
|
||||||
.action {
|
.card {
|
||||||
display: inline-block;
|
background: transparent !important;
|
||||||
color: $gray-200;
|
margin-bottom: 0 !important;
|
||||||
margin-right: 1em;
|
}
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
:hover {
|
.message-card:not(.system-message) {
|
||||||
cursor: pointer;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.mentioned-icon {
|
||||||
color: $gray-300;
|
width: 16px;
|
||||||
margin-right: .2em;
|
height: 16px;
|
||||||
width: 16px;
|
border-radius: 50%;
|
||||||
}
|
background-color: $purple-500;
|
||||||
|
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
|
||||||
|
position: absolute;
|
||||||
|
right: -.5em;
|
||||||
|
top: -.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-hidden {
|
||||||
|
margin-left: 1.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: $purple-300;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
color: $purple-400;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.active {
|
.message-card {
|
||||||
color: $purple-300;
|
border-radius: 7px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem 0.75rem 0.5rem 1rem;
|
||||||
|
|
||||||
.svg-icon {
|
&.system-message {
|
||||||
color: $purple-400;
|
padding-top: 0.5rem;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body {
|
.card-body {
|
||||||
padding: 0.75rem 1.25rem 0.75rem 1.25rem;
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $gray-200;
|
color: $gray-100;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,49 +240,173 @@
|
|||||||
min-height: 0rem;
|
min-height: 0rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
.card-menu {
|
||||||
margin-bottom: 0.5rem;
|
position: absolute;
|
||||||
margin-top: 0.5rem;
|
top: 0;
|
||||||
}
|
right: 0;
|
||||||
|
|
||||||
.reported {
|
&:not(.show) {
|
||||||
margin-top: 18px;
|
display: none;
|
||||||
color: $red-50;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body:hover {
|
||||||
|
.card-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reported {
|
||||||
|
margin-top: 18px;
|
||||||
|
color: $red-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectListItem:not(:hover) .svg-icon.icon-16.color {
|
||||||
|
color: #{$gray-100}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-hover--red {
|
||||||
|
--hover-color: #{$maroon-50};
|
||||||
|
--hover-background: #{rgba($red-500, 0.25)};
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-sent-message {
|
||||||
|
border: 1px solid $purple-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-message {
|
||||||
|
border: 1px solid $purple-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-received-message {
|
||||||
|
border: 1px solid $gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-menu {
|
||||||
|
// icon-color is the menu icon itself
|
||||||
|
--icon-color: #{$gray-100};
|
||||||
|
|
||||||
|
--dropdown-item-hover-icon-color: #{$gray-100};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--icon-color: #{$purple-300};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIcon {
|
||||||
|
width: 4px;
|
||||||
|
height: 1rem;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-message-body {
|
||||||
|
line-height: 1.71;
|
||||||
|
text-align: center;
|
||||||
|
color: $purple-300;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import escapeRegExp from 'lodash/escapeRegExp';
|
||||||
|
import { CHAT_FLAG_FROM_SHADOW_MUTE, CHAT_FLAG_LIMIT_FOR_HIDING } from '@/../../common/script/constants';
|
||||||
import externalLinks from '../../mixins/externalLinks';
|
import externalLinks from '../../mixins/externalLinks';
|
||||||
|
|
||||||
|
import { CopyToClipboardMixin } from '@/mixins/copyToClipboard';
|
||||||
|
|
||||||
import renderWithMentions from '@/libs/renderWithMentions';
|
import renderWithMentions from '@/libs/renderWithMentions';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import userLink from '../userLink';
|
import userLink from '../userLink';
|
||||||
|
|
||||||
import deleteIcon from '@/assets/svg/delete.svg';
|
import deleteIcon from '@/assets/svg/delete.svg';
|
||||||
import reportIcon from '@/assets/svg/report.svg';
|
import reportIcon from '@/assets/svg/report.svg';
|
||||||
|
import menuIcon from '@/assets/svg/menu.svg';
|
||||||
|
import { userStateMixin } from '@/mixins/userState';
|
||||||
|
import copyIcon from '@/assets/svg/copy.svg';
|
||||||
|
import LikeButton from '@/components/messages/likeButton.vue';
|
||||||
|
|
||||||
|
const LikeLogicMixin = {
|
||||||
|
computed: {
|
||||||
|
likeCount () {
|
||||||
|
const message = this.msg;
|
||||||
|
if (!message.likes) return 0;
|
||||||
|
|
||||||
|
let likeCount = 0;
|
||||||
|
for (const key of Object.keys(message.likes)) {
|
||||||
|
const like = message.likes[key];
|
||||||
|
if (like) likeCount += 1;
|
||||||
|
}
|
||||||
|
return likeCount;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
async like () {
|
||||||
|
const message = cloneDeep(this.msg);
|
||||||
|
|
||||||
|
await this.$store.dispatch('chat:like', {
|
||||||
|
groupId: this.groupId,
|
||||||
|
chatMessageId: this.privateMessageMode ? message.uniqueMessageId : message.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
message.likes[this.user._id] = !message.likes[this.user._id];
|
||||||
|
|
||||||
|
this.$emit('message-liked', message);
|
||||||
|
this.$root.$emit('bv::hide::tooltip');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
LikeButton,
|
||||||
userLink,
|
userLink,
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
timeAgo (value) {
|
timeAgo (value) {
|
||||||
return moment(value).fromNow();
|
return moment(value).fromNow();
|
||||||
},
|
},
|
||||||
|
date (value) {
|
||||||
|
// @TODO: Vue doesn't support this so we cant user preference
|
||||||
|
return moment(value).toDate().toString();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mixins: [externalLinks],
|
mixins: [
|
||||||
|
externalLinks, userStateMixin, LikeLogicMixin,
|
||||||
|
CopyToClipboardMixin,
|
||||||
|
],
|
||||||
props: {
|
props: {
|
||||||
msg: {},
|
msg: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
groupId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
privateMessageMode: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
userSentMessage: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
delete: deleteIcon,
|
delete: deleteIcon,
|
||||||
report: reportIcon,
|
report: reportIcon,
|
||||||
|
copy: copyIcon,
|
||||||
|
menuIcon,
|
||||||
}),
|
}),
|
||||||
reported: false,
|
reported: false,
|
||||||
};
|
};
|
||||||
@@ -175,19 +416,100 @@ export default {
|
|||||||
isMessageReported () {
|
isMessageReported () {
|
||||||
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
|
return (this.msg.flags && this.msg.flags[this.user.id]) || this.reported;
|
||||||
},
|
},
|
||||||
|
messageDateForSystemMessage () {
|
||||||
|
return this.isSystemMessage ? this.messageDate : '';
|
||||||
|
},
|
||||||
messageDate () {
|
messageDate () {
|
||||||
const date = moment(this.msg.timestamp).toDate();
|
const date = moment(this.msg.timestamp).toDate();
|
||||||
return date.toString();
|
return date.toString();
|
||||||
},
|
},
|
||||||
|
userIsModerator () {
|
||||||
|
return this.hasPermission(this.user, 'moderator');
|
||||||
|
},
|
||||||
|
isSystemMessage () {
|
||||||
|
return this.msg.uuid === 'system';
|
||||||
|
},
|
||||||
|
canLikeMessage () {
|
||||||
|
if (this.isSystemMessage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.privateMessageMode) {
|
||||||
|
return Boolean(this.msg.uniqueMessageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.msg.id;
|
||||||
|
},
|
||||||
|
canDeleteMessage () {
|
||||||
|
return this.privateMessageMode
|
||||||
|
|| this.msg.uuid === this.user._id
|
||||||
|
|| this.userIsModerator;
|
||||||
|
},
|
||||||
|
canReportMessage () {
|
||||||
|
if (this.privateMessageMode) {
|
||||||
|
return !this.isMessageReported;
|
||||||
|
}
|
||||||
|
return (this.user.flags.communityGuidelinesAccepted && this.msg.uuid !== 'system')
|
||||||
|
&& (!this.isMessageReported || this.userIsModerator);
|
||||||
|
},
|
||||||
|
isUserMentioned () {
|
||||||
|
const message = this.msg;
|
||||||
|
|
||||||
|
if (message.highlight) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user } = this;
|
||||||
|
const displayName = user.profile.name;
|
||||||
|
const { username } = user.auth.local;
|
||||||
|
const pattern = `@(${escapeRegExp(displayName)}|${escapeRegExp(username)})(\\b)`;
|
||||||
|
message.highlight = new RegExp(pattern, 'i').test(message.text);
|
||||||
|
|
||||||
|
return message.highlight;
|
||||||
|
},
|
||||||
|
flagCountDescription () {
|
||||||
|
if (!this.msg.flagCount) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.msg.flagCount < CHAT_FLAG_LIMIT_FOR_HIDING) {
|
||||||
|
return 'Message flagged once, not hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.msg.flagCount < CHAT_FLAG_FROM_SHADOW_MUTE) {
|
||||||
|
return 'Message hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Message hidden (shadow-muted)';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$emit('message-card-mounted');
|
this.$emit('message-card-mounted');
|
||||||
this.handleExternalLinks();
|
this.handleExternalLinks();
|
||||||
|
this.mapProfileLinksToModal();
|
||||||
},
|
},
|
||||||
updated () {
|
updated () {
|
||||||
this.handleExternalLinks();
|
this.handleExternalLinks();
|
||||||
|
this.mapProfileLinksToModal();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
mapProfileLinksToModal () {
|
||||||
|
const links = this.$refs.markdownContainer.getElementsByTagName('a');
|
||||||
|
for (let i = 0; i < links.length; i += 1) {
|
||||||
|
let link = links[i].pathname;
|
||||||
|
|
||||||
|
// Internet Explorer does not provide the leading slash character in the pathname
|
||||||
|
link = link.charAt(0) === '/' ? link : `/${link}`;
|
||||||
|
|
||||||
|
if (link.startsWith('/profile/')) {
|
||||||
|
links[i].onclick = ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.$router.push({ path: link });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
report () {
|
report () {
|
||||||
this.$root.$on('habitica:report-result', data => {
|
this.$root.$on('habitica:report-result', data => {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
@@ -199,16 +521,29 @@ export default {
|
|||||||
|
|
||||||
this.$root.$emit('habitica::report-chat', {
|
this.$root.$emit('habitica::report-chat', {
|
||||||
message: this.msg,
|
message: this.msg,
|
||||||
groupId: 'privateMessage',
|
groupId: this.groupId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async remove () {
|
async remove () {
|
||||||
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) return; // eslint-disable-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
|
if (!window.confirm(this.$t('areYouSureDeleteMessage'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const message = this.msg;
|
const message = this.msg;
|
||||||
this.$emit('message-removed', message);
|
this.$emit('message-removed', message);
|
||||||
|
|
||||||
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
|
if (this.privateMessageMode) {
|
||||||
|
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
|
||||||
|
} else {
|
||||||
|
await this.$store.dispatch('chat:deleteChat', {
|
||||||
|
groupId: this.groupId,
|
||||||
|
chatId: message.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
copy (message) {
|
||||||
|
this.mixinCopyToClipboard(message.text, this.$t('messageCopiedToClipboard'));
|
||||||
},
|
},
|
||||||
parseMarkdown (text) {
|
parseMarkdown (text) {
|
||||||
return renderWithMentions(text, this.user);
|
return renderWithMentions(text, this.user);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="container"
|
ref="container"
|
||||||
class="container-fluid"
|
class="message-list"
|
||||||
>
|
>
|
||||||
<div class="row loadmore">
|
<div class="loadmore">
|
||||||
<div v-if="canLoadMore && !isLoading">
|
<div v-if="canLoadMore && !isLoading">
|
||||||
<div class="loadmore-divider-holder">
|
<div class="loadmore-divider-holder">
|
||||||
<div class="loadmore-divider"></div>
|
<div class="loadmore-divider"></div>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="(msg) in messages"
|
v-for="(msg) in messages"
|
||||||
:key="msg.id"
|
:key="msg.id"
|
||||||
class="row message-row"
|
class="message-row"
|
||||||
:class="{ 'margin-right': user._id !== msg.uuid}"
|
:class="{ 'margin-right': user._id !== msg.uuid}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -39,28 +39,33 @@
|
|||||||
class="avatar-left"
|
class="avatar-left"
|
||||||
:member="conversationOpponentUser"
|
:member="conversationOpponentUser"
|
||||||
:avatar-only="true"
|
:avatar-only="true"
|
||||||
:override-top-padding="'14px'"
|
:show-weapon="true"
|
||||||
|
:debug-mode="false"
|
||||||
|
:height="null"
|
||||||
|
:override-top-padding="'0'"
|
||||||
:hide-class-badge="true"
|
:hide-class-badge="true"
|
||||||
@click.native="showMemberModal(msg.uuid)"
|
@click.native="showMemberModal(msg.uuid)"
|
||||||
/>
|
/>
|
||||||
<div
|
<message-card
|
||||||
class="card"
|
:msg="msg"
|
||||||
:class="{'card-right': user._id !== msg.uuid, 'card-left': user._id === msg.uuid}"
|
:user-sent-message="user._id === msg.uuid"
|
||||||
>
|
:group-id="'privateMessage'"
|
||||||
<message-card
|
:private-message-mode="true"
|
||||||
:msg="msg"
|
@message-liked="messageLiked"
|
||||||
@message-removed="messageRemoved"
|
@message-removed="messageRemoved"
|
||||||
@show-member-modal="showMemberModal"
|
@show-member-modal="showMemberModal"
|
||||||
@message-card-mounted="itemWasMounted"
|
@message-card-mounted="itemWasMounted"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<avatar
|
<avatar
|
||||||
v-if="user && user._id === msg.uuid"
|
v-if="user && user._id === msg.uuid"
|
||||||
class="avatar-right"
|
class="avatar-right"
|
||||||
:member="user"
|
:member="user"
|
||||||
|
:height="null"
|
||||||
:avatar-only="true"
|
:avatar-only="true"
|
||||||
|
:show-weapon="true"
|
||||||
|
:debug-mode="false"
|
||||||
:hide-class-badge="true"
|
:hide-class-badge="true"
|
||||||
:override-top-padding="'14px'"
|
:override-top-padding="'0'"
|
||||||
@click.native="showMemberModal(msg.uuid)"
|
@click.native="showMemberModal(msg.uuid)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,121 +74,110 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
.avatar {
|
.avatar-left, .avatar-right {
|
||||||
width: 170px;
|
align-self: center;
|
||||||
min-width: 8rem;
|
|
||||||
height: 120px;
|
::v-deep .character-sprites {
|
||||||
padding-top: 0 !important;
|
margin-bottom: -5px !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
margin-top: -1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-right {
|
::v-deep .avatar {
|
||||||
margin-left: -1rem;
|
margin-left: -1.75rem;
|
||||||
|
margin-right: -0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep .character-sprites {
|
.card {
|
||||||
margin-right: 1rem !important;
|
border: 0px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0rem;
|
||||||
|
width: 684px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 10px;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-row {
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
|
||||||
|
&:not(.margin-right) {
|
||||||
|
.d-flex {
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.hr {
|
||||||
border: 0px;
|
width: 100%;
|
||||||
margin-bottom: 1rem;
|
height: 20px;
|
||||||
padding: 0rem;
|
border-bottom: 1px solid $gray-500;
|
||||||
width: 684px;
|
text-align: center;
|
||||||
}
|
margin: 2em 0;
|
||||||
.message-row {
|
}
|
||||||
margin-left: 12px;
|
|
||||||
margin-right: 12px;
|
|
||||||
|
|
||||||
&:not(.margin-right) {
|
.hr-middle {
|
||||||
.d-flex {
|
font-size: 16px;
|
||||||
justify-content: flex-end;
|
font-weight: bold;
|
||||||
}
|
font-family: 'Roboto Condensed';
|
||||||
}
|
line-height: 1.5;
|
||||||
}
|
text-align: center;
|
||||||
@media only screen and (max-width: 1200px) {
|
color: $gray-200;
|
||||||
.card {
|
background-color: $gray-700;
|
||||||
width: 100%;
|
padding: .2em;
|
||||||
}
|
margin-top: .2em;
|
||||||
}
|
display: inline-block;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: 1400px) {
|
.loadmore {
|
||||||
.message-row {
|
justify-content: center;
|
||||||
margin-left: -15px;
|
margin-right: 12px;
|
||||||
margin-right: -30px;
|
margin-top: 12px;
|
||||||
}
|
margin-bottom: 24px;
|
||||||
}
|
|
||||||
|
|
||||||
.card-left {
|
> div {
|
||||||
border: 1px solid $purple-500;
|
display: flex;
|
||||||
}
|
|
||||||
|
|
||||||
.card-right {
|
|
||||||
border: 1px solid $gray-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hr {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
align-items: center;
|
||||||
border-bottom: 1px solid $gray-500;
|
|
||||||
text-align: center;
|
|
||||||
margin: 2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hr-middle {
|
button {
|
||||||
font-size: 16px;
|
text-align: center;
|
||||||
font-weight: bold;
|
color: $gray-50;
|
||||||
font-family: 'Roboto Condensed';
|
|
||||||
line-height: 1.5;
|
|
||||||
text-align: center;
|
|
||||||
color: $gray-200;
|
|
||||||
background-color: $gray-700;
|
|
||||||
padding: .2em;
|
|
||||||
margin-top: .2em;
|
|
||||||
display: inline-block;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadmore {
|
|
||||||
justify-content: center;
|
|
||||||
margin-right: 12px;
|
|
||||||
margin-top: 12px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
button {
|
|
||||||
text-align: center;
|
|
||||||
color: $gray-50;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.loadmore-divider-holder {
|
.loadmore-divider-holder {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
margin-right: 24px;
|
margin-right: 24px;
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.loadmore-divider {
|
.loadmore-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
border-top: 1px $gray-500 solid;
|
border-top: 1px $gray-500 solid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -280,6 +274,9 @@ export default {
|
|||||||
// container.style.overflowY = 'scroll';
|
// container.style.overflowY = 'scroll';
|
||||||
}
|
}
|
||||||
}, 50),
|
}, 50),
|
||||||
|
messageLiked (message) {
|
||||||
|
this.$emit('message-liked', message);
|
||||||
|
},
|
||||||
messageRemoved (message) {
|
messageRemoved (message) {
|
||||||
this.$emit('message-removed', message);
|
this.$emit('message-removed', message);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -692,7 +692,7 @@
|
|||||||
<div class="form-inline clearfix">
|
<div class="form-inline clearfix">
|
||||||
<Sprite
|
<Sprite
|
||||||
class="pull-left"
|
class="pull-left"
|
||||||
:class="'inventory_quest_scroll_' + item.key"
|
:image-name="'inventory_quest_scroll_' + item.key"
|
||||||
style="margin-right: 10px"
|
style="margin-right: 10px"
|
||||||
/>
|
/>
|
||||||
<p>{{ item.text() }}</p>
|
<p>{{ item.text() }}</p>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export default {
|
|||||||
if (lastPublishedPost) this.posts.push(lastPublishedPost);
|
if (lastPublishedPost) this.posts.push(lastPublishedPost);
|
||||||
|
|
||||||
// If the user is authorized, show any draft
|
// If the user is authorized, show any draft
|
||||||
if (this.user && this.user.contributor.newsPoster) {
|
if (this.user && (this.user.permissions.news || this.user.permissions.fullAccess)) {
|
||||||
this.posts.unshift(
|
this.posts.unshift(
|
||||||
...postsFromServer
|
...postsFromServer
|
||||||
.filter(p => !p.published || moment().isBefore(p.publishDate)),
|
.filter(p => !p.published || moment().isBefore(p.publishDate)),
|
||||||
|
|||||||
@@ -843,7 +843,6 @@ export default {
|
|||||||
purchasedPlanIdInfo () {
|
purchasedPlanIdInfo () {
|
||||||
if (!this.subscriptionBlocks[this.user.purchased.plan.planId]) {
|
if (!this.subscriptionBlocks[this.user.purchased.plan.planId]) {
|
||||||
// @TODO: find which subs are in the common
|
// @TODO: find which subs are in the common
|
||||||
// console.log(this.subscriptionBlocks
|
|
||||||
// [this.user.purchased.plan.planId]); // eslint-disable-line
|
// [this.user.purchased.plan.planId]); // eslint-disable-line
|
||||||
return {
|
return {
|
||||||
price: 0,
|
price: 0,
|
||||||
|
|||||||
@@ -27,27 +27,15 @@
|
|||||||
@changedPosition="tabSelected($event)"
|
@changedPosition="tabSelected($event)"
|
||||||
>
|
>
|
||||||
<div slot="right-item">
|
<div slot="right-item">
|
||||||
<div
|
<a
|
||||||
v-once
|
v-once
|
||||||
id="petLikeToEatMarket"
|
id="petLikeToEatMarket"
|
||||||
class="drawer-help-text"
|
class="drawer-help-text"
|
||||||
|
href="/static/faq#pet-foods"
|
||||||
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span>{{ $t('petLikeToEat') + ' ' }}</span>
|
<span>{{ $t('petLikeToEat') }}</span>
|
||||||
<span
|
</a>
|
||||||
class="svg-icon inline icon-16"
|
|
||||||
v-html="icons.information"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
<b-popover
|
|
||||||
target="petLikeToEatMarket"
|
|
||||||
:placement="'top'"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-once
|
|
||||||
class="popover-content-text"
|
|
||||||
v-html="$t('petLikeToEatText')"
|
|
||||||
></div>
|
|
||||||
</b-popover>
|
|
||||||
</div>
|
</div>
|
||||||
</drawer-header-tabs>
|
</drawer-header-tabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,7 +68,6 @@
|
|||||||
import _filter from 'lodash/filter';
|
import _filter from 'lodash/filter';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import inventoryUtils from '@/mixins/inventoryUtils';
|
import inventoryUtils from '@/mixins/inventoryUtils';
|
||||||
import svgInformation from '@/assets/svg/information.svg';
|
|
||||||
|
|
||||||
import Drawer from '@/components/ui/drawer';
|
import Drawer from '@/components/ui/drawer';
|
||||||
import DrawerSlider from '@/components/ui/drawerSlider';
|
import DrawerSlider from '@/components/ui/drawerSlider';
|
||||||
@@ -127,10 +114,6 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
selectedDrawerTab: this.defaultSelectedTab,
|
selectedDrawerTab: this.defaultSelectedTab,
|
||||||
|
|
||||||
icons: Object.freeze({
|
|
||||||
information: svgInformation,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="currency of currencies"
|
v-for="currency of currencies"
|
||||||
:key="currency.key"
|
:key="currency.key"
|
||||||
|
:needed-currency-only="neededCurrencyOnly"
|
||||||
class="d-flex align-items-center"
|
class="d-flex align-items-center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -54,6 +55,9 @@ export default {
|
|||||||
amountNeeded: {
|
amountNeeded: {
|
||||||
type: Number,
|
type: Number,
|
||||||
},
|
},
|
||||||
|
neededCurrencyOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -66,34 +70,34 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currencies () {
|
currencies () {
|
||||||
const currencies = [];
|
const currencies = [{
|
||||||
currencies.push({
|
|
||||||
type: 'hourglasses',
|
type: 'hourglasses',
|
||||||
icon: this.icons.hourglasses,
|
icon: this.icons.hourglasses,
|
||||||
value: this.userHourglasses,
|
value: this.userHourglasses,
|
||||||
});
|
},
|
||||||
|
|
||||||
currencies.push({
|
{
|
||||||
type: 'gems',
|
type: 'gems',
|
||||||
icon: this.icons.gem,
|
icon: this.icons.gem,
|
||||||
value: this.userGems,
|
value: this.userGems,
|
||||||
});
|
},
|
||||||
|
|
||||||
currencies.push({
|
{
|
||||||
type: 'gold',
|
type: 'gold',
|
||||||
icon: this.icons.gold,
|
icon: this.icons.gold,
|
||||||
value: this.userGold,
|
value: this.userGold,
|
||||||
});
|
}];
|
||||||
|
|
||||||
for (const currency of currencies) {
|
for (const currency of currencies) {
|
||||||
if (
|
if (currency.type === this.currencyNeeded
|
||||||
currency.type === this.currencyNeeded
|
&& !this.enoughCurrency(this.currencyNeeded, this.amountNeeded)
|
||||||
&& !this.enoughCurrency(this.currencyNeeded, this.amountNeeded)
|
|
||||||
) {
|
) {
|
||||||
currency.notEnough = true;
|
currency.notEnough = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.neededCurrencyOnly) {
|
||||||
|
return currencies.filter(curr => curr.type === this.currencyNeeded);
|
||||||
|
}
|
||||||
return currencies;
|
return currencies;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const QuestHelperMixin = {
|
|||||||
case 'quests':
|
case 'quests':
|
||||||
return `inventory_quest_scroll_${drop.key}`;
|
return `inventory_quest_scroll_${drop.key}`;
|
||||||
case 'mounts':
|
case 'mounts':
|
||||||
return `Mount_Icon_${drop.key}`;
|
return `stable_Mount_Icon_${drop.key}`;
|
||||||
case 'pets':
|
case 'pets':
|
||||||
return `stable_Pet-${drop.key}`;
|
return `stable_Pet-${drop.key}`;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -31,13 +31,6 @@
|
|||||||
:
|
:
|
||||||
<a href="mailto:admin@habitica.com">admin@habitica.com</a>
|
<a href="mailto:admin@habitica.com">admin@habitica.com</a>
|
||||||
<br>
|
<br>
|
||||||
{{ $t('generalQuestionsSite') }}
|
|
||||||
:
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
@click.prevent="openBugReportModal(true)"
|
|
||||||
> {{ $t('askQuestion') }}</a>
|
|
||||||
<br>
|
|
||||||
{{ $t('businessInquiries') }}
|
{{ $t('businessInquiries') }}
|
||||||
:
|
:
|
||||||
<a href="mailto:admin@habitica.com">admin@habitica.com</a>
|
<a href="mailto:admin@habitica.com">admin@habitica.com</a>
|
||||||
@@ -54,10 +47,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import { goToModForm } from '@/libs/modform';
|
import { goToModForm } from '@/libs/modform';
|
||||||
import reportBug from '@/mixins/reportBug.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [reportBug],
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
user: 'user.data',
|
user: 'user.data',
|
||||||
|
|||||||
@@ -66,16 +66,13 @@
|
|||||||
class="nav-link"
|
class="nav-link"
|
||||||
>{{ $t('presskit') }}</a>
|
>{{ $t('presskit') }}</a>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<li class="nav-item">
|
||||||
class="nav-item"
|
|
||||||
tag="li"
|
|
||||||
to="/static/contact"
|
|
||||||
>
|
|
||||||
<a
|
<a
|
||||||
v-once
|
v-once
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
|
href="mailto:admin@habitica.com"
|
||||||
>{{ $t('contactUs') }}</a>
|
>{{ $t('contactUs') }}</a>
|
||||||
</router-link>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul
|
<ul
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.twitter svg {
|
.bluesky svg {
|
||||||
background-color: $purple-50;
|
background-color: $purple-50;
|
||||||
fill: $purple-500;
|
fill: $purple-500;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet"
|
href="https://github.com/HabitRPG/habitica/wiki/Markdown-in-Habitica"
|
||||||
:class="cssClass('headings')"
|
:class="cssClass('headings')"
|
||||||
>{{ $t('markdownHelpLink') }}</a>
|
>{{ $t('markdownHelpLink') }}</a>
|
||||||
</small>
|
</small>
|
||||||
|
|||||||
@@ -129,6 +129,12 @@
|
|||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
padding-left: 24px;
|
padding-left: 24px;
|
||||||
padding-right: 24px;
|
padding-right: 24px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
line-height: 1.33;
|
||||||
|
color: $gray-500;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-tab {
|
.drawer-tab {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
ref="textInput"
|
||||||
:value="value"
|
:value="value"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
:type="inputType"
|
:type="inputType"
|
||||||
@@ -29,19 +30,23 @@
|
|||||||
}"
|
}"
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
:aria-readonly="readonly"
|
:aria-readonly="readonly"
|
||||||
|
autocomplete="off"
|
||||||
|
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@keyup="handleChange"
|
@keyup="handleChange"
|
||||||
|
@keyup.enter="$emit('enter')"
|
||||||
@blur="$emit('blur')"
|
@blur="$emit('blur')"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<template v-if="!hideErrorLine">
|
||||||
v-for="issue in invalidIssues"
|
<div
|
||||||
:key="issue"
|
v-for="issue in invalidIssues"
|
||||||
class="input-error"
|
:key="issue"
|
||||||
>
|
class="input-error"
|
||||||
{{ issue }}
|
>
|
||||||
</div>
|
{{ issue }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -85,6 +90,10 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
hideErrorLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -107,6 +116,9 @@ export default {
|
|||||||
this.wasChanged = true;
|
this.wasChanged = true;
|
||||||
this.$emit('update:value', value);
|
this.$emit('update:value', value);
|
||||||
},
|
},
|
||||||
|
focus () {
|
||||||
|
this.$refs.textInput.focus();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -128,4 +140,12 @@ export default {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* this removes safari "save username" UI, we only search for one, we dont want to save it */
|
||||||
|
input::-webkit-contacts-auto-fill-button,
|
||||||
|
input::-webkit-credentials-auto-fill-button {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -29,20 +29,12 @@
|
|||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
.user-link { // this is the user name
|
.user-link { // this is the user name
|
||||||
font-family: 'Roboto Condensed', sans-serif;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
font-size: 14px;
|
||||||
font-size: 16px;
|
line-height: 1.71;
|
||||||
|
display: inline-flex !important;
|
||||||
// currently used in the member-details-new.vue
|
|
||||||
&.smaller {
|
|
||||||
font-family: Roboto;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1.71;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.no-tier {
|
&.no-tier {
|
||||||
color: $gray-50;
|
color: $gray-50;
|
||||||
@@ -111,7 +103,6 @@ export default {
|
|||||||
'backer',
|
'backer',
|
||||||
'contributor',
|
'contributor',
|
||||||
'hideTooltip',
|
'hideTooltip',
|
||||||
'smallerStyle',
|
|
||||||
'showBuffed',
|
'showBuffed',
|
||||||
'context',
|
'context',
|
||||||
],
|
],
|
||||||
@@ -173,7 +164,7 @@ export default {
|
|||||||
return this.hideTooltip ? '' : achievementsLib.getContribText(this.contributor, this.isNPC) || '';
|
return this.hideTooltip ? '' : achievementsLib.getContribText(this.contributor, this.isNPC) || '';
|
||||||
},
|
},
|
||||||
levelStyle () {
|
levelStyle () {
|
||||||
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)} ${this.smallerStyle ? 'smaller' : ''}`;
|
return `${this.userLevelStyleFromLevel(this.level, this.isNPC)}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export function createAnimal (egg, potion, type, _content, userItems) {
|
|||||||
imageName: type === 'pet' ? `stable_Pet-${animalKey}` : `stable_Mount_Icon_${animalKey}`,
|
imageName: type === 'pet' ? `stable_Pet-${animalKey}` : `stable_Mount_Icon_${animalKey}`,
|
||||||
eggKey: egg.key,
|
eggKey: egg.key,
|
||||||
eggName: getText(egg.text),
|
eggName: getText(egg.text),
|
||||||
|
mountName: getText(egg.mountText),
|
||||||
potionKey: potion.key,
|
potionKey: potion.key,
|
||||||
potionName: getText(potion.text),
|
potionName: getText(potion.text),
|
||||||
name: _content[`${type}Info`][animalKey].text(),
|
name: _content[`${type}Info`][animalKey].text(),
|
||||||
|
|||||||
102
website/client/src/mixins/autoCompleteHelper.js
Normal file
102
website/client/src/mixins/autoCompleteHelper.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
export const autoCompleteHelperMixin = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
mixinData: {
|
||||||
|
autoComplete: {
|
||||||
|
caretPosition: 0,
|
||||||
|
coords: {
|
||||||
|
TOP: 0,
|
||||||
|
LEFT: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
autoCompleteMixinHandleTab (e) {
|
||||||
|
if (this.$refs.autocomplete.searchActive) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this.$refs.autocomplete.selectPrevious();
|
||||||
|
} else {
|
||||||
|
this.$refs.autocomplete.selectNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
autoCompleteMixinHandleEscape (e) {
|
||||||
|
if (this.$refs.autocomplete.searchActive) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.$refs.autocomplete.cancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
autoCompleteMixinSelectNextAutocomplete (e) {
|
||||||
|
if (this.$refs.autocomplete.searchActive) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.$refs.autocomplete.selectNext();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
autoCompleteMixinSelectPreviousAutocomplete (e) {
|
||||||
|
if (this.$refs.autocomplete.searchActive) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.$refs.autocomplete.selectPrevious();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
autoCompleteMixinSelectAutocomplete (e) {
|
||||||
|
if (this.$refs.autocomplete.searchActive) {
|
||||||
|
if (this.$refs.autocomplete.selected !== null) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.$refs.autocomplete.makeSelection();
|
||||||
|
} else {
|
||||||
|
// no autocomplete selected, newline instead
|
||||||
|
this.$refs.autocomplete.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
autoCompleteMixinUpdateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
|
||||||
|
this._updateCarretPosition(eventUpdate);
|
||||||
|
}, 250),
|
||||||
|
|
||||||
|
autoCompleteMixinResetCoordsPosition () {
|
||||||
|
this.mixinData.autoComplete.coords = {
|
||||||
|
TOP: 0,
|
||||||
|
LEFT: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
||||||
|
_getCoord (e, text) {
|
||||||
|
const caretPosition = text.selectionEnd;
|
||||||
|
this.mixinData.autoComplete.caretPosition = caretPosition;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
const span = document.createElement('span');
|
||||||
|
const copyStyle = getComputedStyle(text);
|
||||||
|
|
||||||
|
[].forEach.call(copyStyle, prop => {
|
||||||
|
div.style[prop] = copyStyle[prop];
|
||||||
|
});
|
||||||
|
|
||||||
|
div.style.position = 'absolute';
|
||||||
|
document.body.appendChild(div);
|
||||||
|
div.textContent = text.value.substr(0, caretPosition);
|
||||||
|
span.textContent = text.value.substr(caretPosition) || '.';
|
||||||
|
div.appendChild(span);
|
||||||
|
this.mixinData.autoComplete.coords = {
|
||||||
|
TOP: span.offsetTop,
|
||||||
|
LEFT: span.offsetLeft,
|
||||||
|
};
|
||||||
|
document.body.removeChild(div);
|
||||||
|
},
|
||||||
|
_updateCarretPosition (eventUpdate) {
|
||||||
|
const text = eventUpdate.target;
|
||||||
|
this._getCoord(eventUpdate, text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import notifications from './notifications';
|
import { NotificationMixins } from './notifications';
|
||||||
|
|
||||||
export default {
|
export const CopyToClipboardMixin = {
|
||||||
mixins: [notifications],
|
mixins: [NotificationMixins],
|
||||||
methods: {
|
methods: {
|
||||||
async mixinCopyToClipboard (valueToCopy, notificationToShow = null) {
|
async mixinCopyToClipboard (valueToCopy, notificationToShow = null) {
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
@@ -21,3 +21,5 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default CopyToClipboardMixin;
|
||||||
|
|||||||
@@ -2,54 +2,55 @@ import includes from 'lodash/includes';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
foolPet (pet) {
|
foolPet (pet, prank) {
|
||||||
const SPECIAL_PETS = [
|
const SPECIAL_PETS = [
|
||||||
'Wolf-Veteran',
|
|
||||||
'Wolf-Cerberus',
|
|
||||||
'Dragon-Hydra',
|
|
||||||
'Turkey-Base',
|
|
||||||
'BearCub-Polar',
|
|
||||||
'MantisShrimp-Base',
|
|
||||||
'JackOLantern-Base',
|
|
||||||
'Mammoth-Base',
|
|
||||||
'Tiger-Veteran',
|
|
||||||
'Phoenix-Base',
|
|
||||||
'Turkey-Gilded',
|
|
||||||
'MagicalBee-Base',
|
|
||||||
'Lion-Veteran',
|
|
||||||
'Gryphon-RoyalPurple',
|
|
||||||
'JackOLantern-Ghost',
|
|
||||||
'Jackalope-RoyalPurple',
|
|
||||||
'Orca-Base',
|
|
||||||
'Bear-Veteran',
|
'Bear-Veteran',
|
||||||
'Hippogriff-Hopeful',
|
'BearCub-Polar',
|
||||||
'Fox-Veteran',
|
|
||||||
'JackOLantern-Glow',
|
|
||||||
'Gryphon-Gryphatrice',
|
|
||||||
'Gryphatrice-Jubilant',
|
|
||||||
'JackOLantern-RoyalPurple',
|
|
||||||
'Cactus-Veteran',
|
'Cactus-Veteran',
|
||||||
|
'Dragon-Hydra',
|
||||||
|
'Dragon-Veteran',
|
||||||
|
'Fox-Veteran',
|
||||||
|
'Gryphatrice-Jubilant',
|
||||||
|
'Gryphon-Gryphatrice',
|
||||||
|
'Gryphon-RoyalPurple',
|
||||||
|
'Hippogriff-Hopeful',
|
||||||
|
'Jackalope-RoyalPurple',
|
||||||
|
'JackOLantern-Base',
|
||||||
|
'JackOLantern-Ghost',
|
||||||
|
'JackOLantern-Glow',
|
||||||
|
'JackOLantern-RoyalPurple',
|
||||||
|
'Lion-Veteran',
|
||||||
|
'MagicalBee-Base',
|
||||||
|
'Mammoth-Base',
|
||||||
|
'MantisShrimp-Base',
|
||||||
|
'Orca-Base',
|
||||||
|
'Phoenix-Base',
|
||||||
|
'Tiger-Veteran',
|
||||||
|
'Turkey-Base',
|
||||||
|
'Turkey-Gilded',
|
||||||
|
'Wolf-Cerberus',
|
||||||
|
'Wolf-Veteran',
|
||||||
];
|
];
|
||||||
const BASE_PETS = [
|
const BASE_PETS = [
|
||||||
'Wolf',
|
|
||||||
'TigerCub',
|
|
||||||
'PandaCub',
|
|
||||||
'LionCub',
|
|
||||||
'Fox',
|
|
||||||
'FlyingPig',
|
|
||||||
'BearCub',
|
'BearCub',
|
||||||
'Dragon',
|
|
||||||
'Cactus',
|
'Cactus',
|
||||||
|
'Dragon',
|
||||||
|
'FlyingPig',
|
||||||
|
'Fox',
|
||||||
|
'LionCub',
|
||||||
|
'PandaCub',
|
||||||
|
'TigerCub',
|
||||||
|
'Wolf',
|
||||||
];
|
];
|
||||||
if (!pet) return 'Pet-TigerCub-Fungi';
|
if (!pet) return `Pet-TigerCub-${prank}`;
|
||||||
if (SPECIAL_PETS.indexOf(pet) !== -1) {
|
if (SPECIAL_PETS.indexOf(pet) !== -1) {
|
||||||
return 'Pet-Dragon-Fungi';
|
return `Pet-Dragon-${prank}`;
|
||||||
}
|
}
|
||||||
const species = pet.slice(0, pet.indexOf('-'));
|
const species = pet.slice(0, pet.indexOf('-'));
|
||||||
if (includes(BASE_PETS, species)) {
|
if (includes(BASE_PETS, species)) {
|
||||||
return `Pet-${species}-Fungi`;
|
return `Pet-${species}-${prank}`;
|
||||||
}
|
}
|
||||||
return 'Pet-BearCub-Fungi';
|
return `Pet-BearCub-${prank}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import userLabel from '../userLabel';
|
import userLabel from '../../components/userLabel.vue';
|
||||||
|
|
||||||
import dots from '@/assets/svg/dots.svg';
|
import dots from '@/assets/svg/dots.svg';
|
||||||
import block from '@/assets/svg/block.svg';
|
import block from '@/assets/svg/block.svg';
|
||||||
@@ -117,7 +117,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors';
|
||||||
|
|
||||||
.action-padding {
|
.action-padding {
|
||||||
height: 24px !important;
|
height: 24px !important;
|
||||||
@@ -153,7 +153,7 @@ export default {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors';
|
||||||
|
|
||||||
.conversation {
|
.conversation {
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="filtersConversations.length > 0"
|
||||||
|
class="conversations"
|
||||||
|
>
|
||||||
|
<conversation-item
|
||||||
|
v-for="conversation in filtersConversations"
|
||||||
|
:key="conversation.key"
|
||||||
|
:active-key="selectedConversation?.key"
|
||||||
|
:contributor="conversation.contributor"
|
||||||
|
:backer="conversation.backer"
|
||||||
|
:uuid="conversation.key"
|
||||||
|
:display-name="conversation.name"
|
||||||
|
:username="conversation.username"
|
||||||
|
:last-message-date="conversation.date"
|
||||||
|
:last-message-text="conversation.lastMessageText
|
||||||
|
? removeTags(parseMarkdown(conversation.lastMessageText)) : ''"
|
||||||
|
@click="selectConversation(conversation.key)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.conversations {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import habiticaMarkdown from 'habitica-markdown';
|
||||||
|
import conversationItem from '@/pages/private-messages/pm-conversation-item.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { conversationItem },
|
||||||
|
props: {
|
||||||
|
filtersConversations: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
selectedConversation: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeTags (html) {
|
||||||
|
const tmp = document.createElement('DIV');
|
||||||
|
tmp.innerHTML = html;
|
||||||
|
return tmp.textContent || tmp.innerText || '';
|
||||||
|
},
|
||||||
|
parseMarkdown (text) {
|
||||||
|
if (!text) return null;
|
||||||
|
return habiticaMarkdown.render(String(text));
|
||||||
|
},
|
||||||
|
selectConversation (conversationKey) {
|
||||||
|
this.$emit('selectConversation', conversationKey);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="pm-disabled-caption text-center"
|
||||||
|
>
|
||||||
|
<h4>{{ disabledTexts.title }}</h4>
|
||||||
|
<p>{{ disabledTexts.description }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.pm-disabled-caption {
|
||||||
|
padding-top: 1.5em;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
h4, p {
|
||||||
|
color: $gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['disabledTexts'],
|
||||||
|
};
|
||||||
|
</script>
|
||||||
71
website/client/src/pages/private-messages/pm-empty-state.vue
Normal file
71
website/client/src/pages/private-messages/pm-empty-state.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="empty-messages m-auto text-center empty-sidebar"
|
||||||
|
>
|
||||||
|
<div class="no-messages-box">
|
||||||
|
<div
|
||||||
|
v-once
|
||||||
|
class="svg-icon envelope mb-4"
|
||||||
|
v-html="icons.mailIcon"
|
||||||
|
></div>
|
||||||
|
<strong
|
||||||
|
v-once
|
||||||
|
class="mb-1"
|
||||||
|
>
|
||||||
|
{{ $t('emptyMessagesLine1') }}
|
||||||
|
</strong>
|
||||||
|
<p v-if="!chatRevoked">
|
||||||
|
{{ $t('emptyMessagesLine2') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-primary mt-4 d-flex align-items-center"
|
||||||
|
@click="$emit('newMessageClicked')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="svg-icon icon-10 color mr-2"
|
||||||
|
v-html="icons.positive"
|
||||||
|
></div>
|
||||||
|
{{ $t('newMessage') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
strong {
|
||||||
|
line-height: 1.71;
|
||||||
|
color: $gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon.icon-10 {
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mailIcon from '@/assets/svg/mail.svg';
|
||||||
|
import positiveIcon from '@/assets/svg/positive.svg';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
chatRevoked: Boolean,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
icons: Object.freeze({
|
||||||
|
mailIcon,
|
||||||
|
positive: positiveIcon,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user