Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42d823ab44 | |||
| f19331cfcc | |||
| b7de7335ed | |||
| 2e00ec5534 | |||
| a3af39ed25 | |||
| b57518732e | |||
| a6a6aac400 | |||
| 48a92e77be | |||
| d2a39a5124 | |||
| 7bead74b49 | |||
| 91e91788ce | |||
| 62c60ce520 | |||
| 423eafbd4d | |||
| 004ab51c46 | |||
| f464403623 | |||
| 0fc66bef4e | |||
| 71636cd25e | |||
| d5efb50d9b | |||
| 510e01effd | |||
| 0e648d85a0 | |||
| 7b562c45cf | |||
| b7a46637d5 | |||
| 284b2cc413 | |||
| 8b69540e71 | |||
| 8d9a4e97a8 | |||
| f31a82c8f2 | |||
| 8bc02e82ee | |||
| 9040f9f04e | |||
| ff82c37d5f | |||
| 37364b0700 | |||
| 11cfb3920a | |||
| f5468d3771 | |||
| 99882d09ab | |||
| 7034d135d5 | |||
| 034c0c9bb5 | |||
| e0711655f0 | |||
| 71e162eed5 | |||
| 8eac8732c5 | |||
| 896a1b74b6 | |||
| 3b36046a6a | |||
| 07991817e7 | |||
| 47b75156fa | |||
| c630486fef | |||
| 0a070316b5 | |||
| f6b34e85df | |||
| 2946f0df15 | |||
| 614d9a920a | |||
| 454524fb5b | |||
| abc0777412 | |||
| 6972eb8f8f | |||
| 535ee2b2a7 | |||
| c9755bee7c | |||
| f810fff6fc | |||
| 5bbe59c52d | |||
| 3f52401384 | |||
| e7944b3d98 | |||
| 08e925e3da | |||
| 16c9e42ad8 | |||
| 0e1d00c95f | |||
| 166f4683ca | |||
| 1fcc0d8d3a | |||
| 8a4c4e10f1 | |||
| 18ed0fe446 | |||
| 1f9ebeb629 | |||
| b9d83122d1 | |||
| d1f7e64156 | |||
| 0f8e7416f8 | |||
| 1c8b0f92df |
@@ -78,6 +78,9 @@
|
||||
"PUSH_CONFIGS": {
|
||||
"GCM_SERVER_API_KEY": "",
|
||||
"APN_ENABLED": "false",
|
||||
"APN_KEY_ID": "xxxxxxxxxx",
|
||||
"APN_KEY": "xxxxxxxxxx",
|
||||
"APN_TEAM_ID": "aaabbbcccd",
|
||||
"FCM_SERVER_API_KEY": ""
|
||||
},
|
||||
"SITE_HTTP_AUTH": {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
|
||||
const migrationName = 'mystery-items-201806.js'; // Update per month
|
||||
const migrationName = 'mystery-items-201807.js'; // Update per month
|
||||
const authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Award this month's mystery items to subscribers
|
||||
*/
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201806', 'head_mystery_201806'];
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201807', 'head_mystery_201807'];
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
let migrationName = '20180724_summer-splash-orcas.js'; // Update per month
|
||||
let authorName = 'Sabe'; // in case script author needs to know when their ...
|
||||
let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
|
||||
|
||||
/*
|
||||
* Award ladder items to participants in this year's Summer Splash festivities
|
||||
*/
|
||||
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING');
|
||||
let dbUsers = monk(CONNECTION_STRING).get('users', { castIds: false });
|
||||
|
||||
function processUsers (lastId) {
|
||||
// specify a query to limit the affected users (empty for all users):
|
||||
let query = {
|
||||
migration: {$ne: migrationName},
|
||||
'auth.timestamps.loggedin': {$gt: new Date('2018-07-01')}, // rerun without date restriction after initial run
|
||||
};
|
||||
|
||||
if (lastId) {
|
||||
query._id = {
|
||||
$gt: lastId,
|
||||
};
|
||||
}
|
||||
|
||||
dbUsers.find(query, {
|
||||
sort: {_id: 1},
|
||||
limit: 250,
|
||||
fields: [
|
||||
'items.mounts',
|
||||
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
|
||||
})
|
||||
.then(updateUsers)
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
return exiting(1, `ERROR! ${ err}`);
|
||||
});
|
||||
}
|
||||
|
||||
let progressCount = 1000;
|
||||
let count = 0;
|
||||
|
||||
function updateUsers (users) {
|
||||
if (!users || users.length === 0) {
|
||||
console.warn('All appropriate users found and modified.');
|
||||
displayData();
|
||||
return;
|
||||
}
|
||||
|
||||
let userPromises = users.map(updateUser);
|
||||
let lastUser = users[users.length - 1];
|
||||
|
||||
return Promise.all(userPromises)
|
||||
.then(() => {
|
||||
processUsers(lastUser._id);
|
||||
});
|
||||
}
|
||||
|
||||
function updateUser (user) {
|
||||
count++;
|
||||
|
||||
let set = {};
|
||||
|
||||
if (user && user.items && user.items.pets && typeof user.items.pets['Orca-Base'] !== 'undefined') {
|
||||
set = {migration: migrationName};
|
||||
} else if (user && user.items && user.items.mounts && typeof user.items.mounts['Orca-Base'] !== 'undefined') {
|
||||
set = {migration: migrationName, 'items.pets.Orca-Base': 5};
|
||||
} else {
|
||||
set = {migration: migrationName, 'items.mounts.Orca-Base': true};
|
||||
}
|
||||
|
||||
dbUsers.update({_id: user._id}, {$set: set});
|
||||
|
||||
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
|
||||
if (user._id === authorUuid) console.warn(`${authorName } processed`);
|
||||
}
|
||||
|
||||
function displayData () {
|
||||
console.warn(`\n${ count } users processed\n`);
|
||||
return exiting(0);
|
||||
}
|
||||
|
||||
function exiting (code, msg) {
|
||||
code = code || 0; // 0 = success
|
||||
if (code && !msg) {
|
||||
msg = 'ERROR!';
|
||||
}
|
||||
if (msg) {
|
||||
if (code) {
|
||||
console.error(msg);
|
||||
} else {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
module.exports = processUsers;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "4.51.1",
|
||||
"version": "4.54.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -801,13 +801,25 @@
|
||||
}
|
||||
},
|
||||
"apn": {
|
||||
"version": "1.7.8",
|
||||
"resolved": "https://registry.npmjs.org/apn/-/apn-1.7.8.tgz",
|
||||
"integrity": "sha1-Hp2kKPtXr6lX5UIjvvc0LALCTNo=",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/apn/-/apn-2.2.0.tgz",
|
||||
"integrity": "sha512-YIypYzPVJA9wzNBLKZ/mq2l1IZX/2FadPvwmSv4ZeR0VH7xdNITQ6Pucgh0Uw6ZZKC+XwheaJ57DFZAhJ0FvPg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"node-forge": "0.6.49",
|
||||
"q": "1.5.1"
|
||||
"debug": "3.1.0",
|
||||
"http2": "https://github.com/node-apn/node-http2/archive/apn-2.1.4.tar.gz",
|
||||
"jsonwebtoken": "8.3.0",
|
||||
"node-forge": "0.7.5",
|
||||
"verror": "1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"append-buffer": {
|
||||
@@ -5036,6 +5048,11 @@
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz",
|
||||
"integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74="
|
||||
},
|
||||
"buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
|
||||
},
|
||||
"buffer-fill": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
|
||||
@@ -8792,6 +8809,14 @@
|
||||
"jsbn": "0.1.1"
|
||||
}
|
||||
},
|
||||
"ecdsa-sig-formatter": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz",
|
||||
"integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@@ -13292,6 +13317,10 @@
|
||||
"sshpk": "1.14.1"
|
||||
}
|
||||
},
|
||||
"http2": {
|
||||
"version": "https://github.com/node-apn/node-http2/archive/apn-2.1.4.tar.gz",
|
||||
"integrity": "sha512-ad4u4I88X9AcUgxCRW3RLnbh7xHWQ1f5HbrXa7gEy2x4Xgq+rq+auGx5I+nUDE2YYuqteGIlbxrwQXkIaYTfnQ=="
|
||||
},
|
||||
"httpntlm": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz",
|
||||
@@ -14768,6 +14797,29 @@
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
|
||||
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk="
|
||||
},
|
||||
"jsonwebtoken": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz",
|
||||
"integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==",
|
||||
"requires": {
|
||||
"jws": "3.1.5",
|
||||
"lodash.includes": "4.3.0",
|
||||
"lodash.isboolean": "3.0.3",
|
||||
"lodash.isinteger": "4.0.4",
|
||||
"lodash.isnumber": "3.0.3",
|
||||
"lodash.isplainobject": "4.0.6",
|
||||
"lodash.isstring": "4.0.1",
|
||||
"lodash.once": "4.1.1",
|
||||
"ms": "2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||
@@ -14806,6 +14858,25 @@
|
||||
"integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==",
|
||||
"dev": true
|
||||
},
|
||||
"jwa": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz",
|
||||
"integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==",
|
||||
"requires": {
|
||||
"buffer-equal-constant-time": "1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.10",
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"jws": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz",
|
||||
"integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==",
|
||||
"requires": {
|
||||
"jwa": "1.1.6",
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"kareem": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.1.0.tgz",
|
||||
@@ -16418,6 +16489,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
|
||||
},
|
||||
"lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
|
||||
},
|
||||
"lodash.initial": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.initial/-/lodash.initial-4.1.1.tgz",
|
||||
@@ -16433,16 +16509,36 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
|
||||
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U="
|
||||
},
|
||||
"lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
|
||||
},
|
||||
"lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
|
||||
},
|
||||
"lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
|
||||
},
|
||||
"lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
|
||||
},
|
||||
"lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
|
||||
},
|
||||
"lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
|
||||
},
|
||||
"lodash.istypedarray": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz",
|
||||
@@ -16484,6 +16580,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
|
||||
"integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ=="
|
||||
},
|
||||
"lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
|
||||
},
|
||||
"lodash.pairs": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pairs/-/lodash.pairs-3.0.1.tgz",
|
||||
@@ -18033,11 +18134,6 @@
|
||||
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.4.1.tgz",
|
||||
"integrity": "sha512-NNY/MpBkALb9jJmjpBlIi6GRoLveLUM0pJzgbp9vY9F7IQEb/HREC/nxrixechcQwd1NevOhJnWWV8QQQRE+OA=="
|
||||
},
|
||||
"mpns": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/mpns/-/mpns-2.1.3.tgz",
|
||||
"integrity": "sha512-gPLNoVqwYoKUmNYZ2shMSdaE2XvHSRxWNzyG4DUi6Av7MSujyeOw/nj61nnQeuV/vke5E0Dni468xn0qxTHIZQ=="
|
||||
},
|
||||
"mquery": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mquery/-/mquery-3.0.0.tgz",
|
||||
@@ -18457,9 +18553,9 @@
|
||||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.6.49",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.49.tgz",
|
||||
"integrity": "sha1-8e6V1ddGI5OP4Z1piqWibVTS9g8="
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz",
|
||||
"integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ=="
|
||||
},
|
||||
"node-gcm": {
|
||||
"version": "0.14.10",
|
||||
@@ -18655,9 +18751,9 @@
|
||||
}
|
||||
},
|
||||
"node-rdkafka": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.3.3.tgz",
|
||||
"integrity": "sha512-2J54zC9+Zj0iRQttmQs1Ubv8aHhmh04XjP3vk39uco7l6tp8BYYHG4XRsoqKOGGKjBLctGpFHr9g97WBE1pTbg==",
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/node-rdkafka/-/node-rdkafka-2.3.4.tgz",
|
||||
"integrity": "sha512-ilaAOrEpDF3TGTlItsxU5pQXG+qjN1gKbhSvs9CoLXZaItt2EN6oU+kEdO6UkRQLKO6/Kv4m296cBrr0JCmiTw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "1.3.0",
|
||||
@@ -20335,11 +20431,6 @@
|
||||
"pinkie": "2.0.4"
|
||||
}
|
||||
},
|
||||
"pipe-event": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pipe-event/-/pipe-event-0.1.0.tgz",
|
||||
"integrity": "sha1-pfXgPlqXsrdJPUsqBgzYPazLmmE="
|
||||
},
|
||||
"pixelsmith": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/pixelsmith/-/pixelsmith-2.2.1.tgz",
|
||||
@@ -22718,19 +22809,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"push-notify": {
|
||||
"version": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
|
||||
"requires": {
|
||||
"apn": "1.7.8",
|
||||
"bluebird": "3.5.1",
|
||||
"lodash": "4.17.10",
|
||||
"mpns": "2.1.3",
|
||||
"node-gcm": "0.14.10",
|
||||
"pipe-event": "0.1.0",
|
||||
"q": "1.5.1",
|
||||
"wns": "0.5.3"
|
||||
}
|
||||
},
|
||||
"pusher": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pusher/-/pusher-1.5.1.tgz",
|
||||
@@ -27870,11 +27948,6 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"wns": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/wns/-/wns-0.5.3.tgz",
|
||||
"integrity": "sha1-APToXPz44zg9y9gYmJBvH2rUhF8="
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.51.1",
|
||||
"version": "4.54.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
@@ -11,6 +11,7 @@
|
||||
"apidoc": "^0.17.5",
|
||||
"autoprefixer": "^8.5.0",
|
||||
"aws-sdk": "^2.239.1",
|
||||
"apn": "^2.2.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.3",
|
||||
@@ -78,7 +79,6 @@
|
||||
"postcss-easy-import": "^3.0.0",
|
||||
"ps-tree": "^1.0.0",
|
||||
"pug": "^2.0.3",
|
||||
"push-notify": "git://github.com/habitrpg/push-notify.git#6bc2b5fdb1bdc9649b9ec1964d79ca50187fc8a9",
|
||||
"pusher": "^1.3.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"sass-loader": "^7.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import requireAgain from 'require-again';
|
||||
import pushNotify from 'push-notify';
|
||||
import apn from 'apn/mock';
|
||||
import nconf from 'nconf';
|
||||
import gcmLib from 'node-gcm'; // works with FCM notifications too
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('pushNotifications', () => {
|
||||
|
||||
sandbox.stub(gcmLib.Sender.prototype, 'send').callsFake(fcmSendSpy);
|
||||
|
||||
sandbox.stub(pushNotify, 'apn').returns({
|
||||
sandbox.stub(apn.Provider.prototype, 'send').returns({
|
||||
on: () => null,
|
||||
send: apnSendSpy,
|
||||
});
|
||||
@@ -104,10 +104,7 @@ describe('pushNotifications', () => {
|
||||
},
|
||||
};
|
||||
|
||||
sendPushNotification(user, details);
|
||||
expect(apnSendSpy).to.have.been.calledOnce;
|
||||
expect(apnSendSpy).to.have.been.calledWithMatch({
|
||||
token: '123',
|
||||
const expectedNotification = new apn.Notification({
|
||||
alert: message,
|
||||
sound: 'default',
|
||||
category: 'fun',
|
||||
@@ -117,6 +114,10 @@ describe('pushNotifications', () => {
|
||||
b: true,
|
||||
},
|
||||
});
|
||||
|
||||
sendPushNotification(user, details);
|
||||
expect(apnSendSpy).to.have.been.calledOnce;
|
||||
expect(apnSendSpy).to.have.been.calledWithMatch(expectedNotification, '123');
|
||||
expect(fcmSendSpy).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -388,6 +388,23 @@ describe('POST /chat', () => {
|
||||
expect(groupMessages[0].id).to.exist;
|
||||
});
|
||||
|
||||
it('creates a chat with a max length of 3000 chars', async () => {
|
||||
const veryLongMessage = `
|
||||
123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789.
|
||||
THIS PART WON'T BE IN THE MESSAGE (over 3000)
|
||||
`;
|
||||
|
||||
const newMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: veryLongMessage});
|
||||
const groupMessages = await user.get(`/groups/${groupWithChat._id}/chat`);
|
||||
|
||||
expect(newMessage.message.id).to.exist;
|
||||
expect(groupMessages[0].id).to.exist;
|
||||
|
||||
expect(newMessage.message.text.length).to.eql(3000);
|
||||
expect(newMessage.message.text).to.not.contain('MESSAGE');
|
||||
expect(groupMessages[0].text.length).to.eql(3000);
|
||||
});
|
||||
|
||||
it('creates a chat with user styles', async () => {
|
||||
const mount = 'test-mount';
|
||||
const pet = 'test-pet';
|
||||
|
||||
@@ -23,6 +23,17 @@ describe('GET /export/userdata.xml', () => {
|
||||
|
||||
]);
|
||||
|
||||
// add pinnedItem
|
||||
await user.get('/user/toggle-pinned-item/marketGear/gear.flat.shield_rogue_5');
|
||||
|
||||
// add a private message
|
||||
let receiver = await generateUser();
|
||||
|
||||
user.post('/members/send-private-message', {
|
||||
message: 'Your first message, hi!',
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let response = await user.get('/export/userdata.xml');
|
||||
let {user: res} = await parseStringAsync(response, {explicitArray: false});
|
||||
|
||||
|
||||
@@ -140,4 +140,89 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
||||
message: t('canOnlyApproveTaskOnce'),
|
||||
});
|
||||
});
|
||||
|
||||
it('completes master task when single-completion task is approved', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
type: 'todo',
|
||||
requiresApproval: true,
|
||||
sharedCompletion: 'singleCompletion',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
|
||||
|
||||
let masterTask = find(groupTasks, (groupTask) => {
|
||||
return groupTask._id === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
expect(masterTask.completed).to.equal(true);
|
||||
});
|
||||
|
||||
it('deletes other assigned user tasks when single-completion task is approved', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
type: 'todo',
|
||||
requiresApproval: true,
|
||||
sharedCompletion: 'singleCompletion',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let member2Tasks = await member2.get('/tasks/user');
|
||||
|
||||
let syncedTask2 = find(member2Tasks, (memberTask) => {
|
||||
return memberTask.group.taskId === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
expect(syncedTask2).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('does not complete master task when not all user tasks are approved if all assigned must complete', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
type: 'todo',
|
||||
requiresApproval: true,
|
||||
sharedCompletion: 'allAssignedCompletion',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}`);
|
||||
|
||||
let masterTask = find(groupTasks, (groupTask) => {
|
||||
return groupTask._id === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
expect(masterTask.completed).to.equal(false);
|
||||
});
|
||||
|
||||
it('completes master task when all user tasks are approved if all assigned must complete', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
type: 'todo',
|
||||
requiresApproval: true,
|
||||
sharedCompletion: 'allAssignedCompletion',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
|
||||
|
||||
let masterTask = find(groupTasks, (groupTask) => {
|
||||
return groupTask._id === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
expect(masterTask.completed).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a user to score an apporoved task', async () => {
|
||||
it('allows a user to score an approved task', async () => {
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, findAssignedTask);
|
||||
|
||||
@@ -137,4 +137,112 @@ describe('POST /tasks/:id/score/:direction', () => {
|
||||
expect(updatedTask.completed).to.equal(true);
|
||||
expect(updatedTask.dateCompleted).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
|
||||
});
|
||||
|
||||
it('completes master task when single-completion task is completed', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
type: 'todo',
|
||||
requiresApproval: false,
|
||||
sharedCompletion: 'singleCompletion',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
|
||||
let syncedTask = find(memberTasks, (memberTask) => {
|
||||
return memberTask.group.taskId === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
|
||||
let masterTask = find(groupTasks, (groupTask) => {
|
||||
return groupTask._id === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
expect(masterTask.completed).to.equal(true);
|
||||
});
|
||||
|
||||
it('deletes other assigned user tasks when single-completion task is completed', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
type: 'todo',
|
||||
requiresApproval: false,
|
||||
sharedCompletion: 'singleCompletion',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
|
||||
let syncedTask = find(memberTasks, (memberTask) => {
|
||||
return memberTask.group.taskId === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
|
||||
let member2Tasks = await member2.get('/tasks/user');
|
||||
|
||||
let syncedTask2 = find(member2Tasks, (memberTask) => {
|
||||
return memberTask.group.taskId === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
expect(syncedTask2).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('does not complete master task when not all user tasks are completed if all assigned must complete', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
type: 'todo',
|
||||
requiresApproval: false,
|
||||
sharedCompletion: 'allAssignedCompletion',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
|
||||
let syncedTask = find(memberTasks, (memberTask) => {
|
||||
return memberTask.group.taskId === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}`);
|
||||
let masterTask = find(groupTasks, (groupTask) => {
|
||||
return groupTask._id === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
expect(masterTask.completed).to.equal(false);
|
||||
});
|
||||
|
||||
it('completes master task when all user tasks are completed if all assigned must complete', async () => {
|
||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||
text: 'shared completion todo',
|
||||
type: 'todo',
|
||||
requiresApproval: false,
|
||||
sharedCompletion: 'allAssignedCompletion',
|
||||
});
|
||||
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||
let memberTasks = await member.get('/tasks/user');
|
||||
let member2Tasks = await member2.get('/tasks/user');
|
||||
let syncedTask = find(memberTasks, (memberTask) => {
|
||||
return memberTask.group.taskId === sharedCompletionTask._id;
|
||||
});
|
||||
let syncedTask2 = find(member2Tasks, (memberTask) => {
|
||||
return memberTask.group.taskId === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||
await member2.post(`/tasks/${syncedTask2._id}/score/up`);
|
||||
|
||||
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
|
||||
let masterTask = find(groupTasks, (groupTask) => {
|
||||
return groupTask._id === sharedCompletionTask._id;
|
||||
});
|
||||
|
||||
expect(masterTask.completed).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,7 +62,11 @@ module.exports = {
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/logout': {
|
||||
'/logout-server': {
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/export': {
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
|
||||
@@ -105,7 +105,7 @@ div
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal, .modal-open {
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ div
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1090 !important; /* Must stay above nav bar */
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
|
||||
.restingInn {
|
||||
@@ -136,7 +136,7 @@ div
|
||||
background-color: $blue-10;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1030;
|
||||
z-index: 1300;
|
||||
display: flex;
|
||||
|
||||
.content {
|
||||
@@ -331,9 +331,33 @@ export default {
|
||||
];
|
||||
if (notificationNotFoundMessage.indexOf(errorMessage) !== -1) snackbarTimeout = true;
|
||||
|
||||
let errorsToShow = [];
|
||||
let usernameCheck = false;
|
||||
let emailCheck = false;
|
||||
let passwordCheck = false;
|
||||
// show only the first error for each param
|
||||
if (errorData.errors) {
|
||||
for (let e of errorData.errors) {
|
||||
if (!usernameCheck && e.param === 'username') {
|
||||
errorsToShow.push(e.message);
|
||||
usernameCheck = true;
|
||||
}
|
||||
if (!emailCheck && e.param === 'email') {
|
||||
errorsToShow.push(e.message);
|
||||
emailCheck = true;
|
||||
}
|
||||
if (!passwordCheck && e.param === 'password') {
|
||||
errorsToShow.push(e.message);
|
||||
passwordCheck = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorsToShow.push(errorMessage);
|
||||
}
|
||||
// dispatch as one snackbar notification
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: errorMessage,
|
||||
text: errorsToShow.join(' '),
|
||||
type: 'error',
|
||||
timeout: snackbarTimeout,
|
||||
});
|
||||
@@ -475,8 +499,16 @@ export default {
|
||||
});
|
||||
|
||||
this.$root.$on('bv::modal::hidden', (bvEvent) => {
|
||||
const modalId = bvEvent.target && bvEvent.target.id;
|
||||
if (!modalId) return;
|
||||
let modalId = bvEvent.target && bvEvent.target.id;
|
||||
|
||||
// sometimes the target isn't passed to the hidden event, fallback is the vueTarget
|
||||
if (!modalId) {
|
||||
modalId = bvEvent.vueTarget && bvEvent.vueTarget.id;
|
||||
}
|
||||
|
||||
if (!modalId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modalStack = this.$store.state.modalStack;
|
||||
|
||||
@@ -493,6 +525,7 @@ export default {
|
||||
|
||||
// Get previous modal
|
||||
const modalBefore = modalOnTop ? modalOnTop.prev : undefined;
|
||||
|
||||
if (modalBefore) this.$root.$emit('bv::show::modal', modalBefore, {fromRoot: true});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,54 +1,90 @@
|
||||
.promo_aquatic_glass_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -370px;
|
||||
background-position: -853px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201807 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -284px -370px;
|
||||
background-position: -995px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_ios {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -394px 0px;
|
||||
width: 325px;
|
||||
height: 336px;
|
||||
background-position: -477px 0px;
|
||||
width: 375px;
|
||||
height: 361px;
|
||||
}
|
||||
.promo_mystery_201806 {
|
||||
.promo_mystery_201807 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -568px -370px;
|
||||
width: 121px;
|
||||
height: 114px;
|
||||
background-position: -995px -442px;
|
||||
width: 114px;
|
||||
height: 120px;
|
||||
}
|
||||
.promo_orcas {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -954px;
|
||||
width: 219px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_seafoam {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -426px -370px;
|
||||
background-position: -853px -442px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_seaserpent {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 476px;
|
||||
height: 364px;
|
||||
}
|
||||
.promo_seasonal_shop_summer {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -812px;
|
||||
background-position: -642px -552px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_splashy_skins {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -365px;
|
||||
width: 375px;
|
||||
height: 186px;
|
||||
}
|
||||
.customize-option.promo_splashy_skins {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -167px -380px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.promo_summer_splash_2018 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -370px;
|
||||
background-position: 0px -365px;
|
||||
width: 141px;
|
||||
height: 588px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -568px -485px;
|
||||
background-position: -995px -563px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.scene_party_healing {
|
||||
.promo_unconventional_armor {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 393px;
|
||||
height: 369px;
|
||||
background-position: -518px -365px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
.scene_pomodoro {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -142px -552px;
|
||||
width: 258px;
|
||||
height: 258px;
|
||||
}
|
||||
.scene_todos {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -401px -552px;
|
||||
width: 240px;
|
||||
height: 195px;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 340 KiB |
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
@@ -1,13 +1,22 @@
|
||||
.markdown {
|
||||
> p {
|
||||
margin-bottom: 0px;
|
||||
p {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 14px;
|
||||
line-height: 1.17;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 4px;
|
||||
margin-top: 6px;
|
||||
color: $gray-10;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,9 +112,10 @@ export default {
|
||||
} catch (e) {} // eslint-disable-line
|
||||
|
||||
try {
|
||||
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
|
||||
let auth = await hello(network).login({
|
||||
scope: 'email',
|
||||
redirect_uri: '', // eslint-disable-line camelcase
|
||||
redirect_uri: redirectUrl, // eslint-disable-line camelcase
|
||||
});
|
||||
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
methods: {
|
||||
async logout () {
|
||||
return await this.$store.dispatch('auth:logout');
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.logout();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -402,11 +402,6 @@ export default {
|
||||
window.location.href = redirectTo;
|
||||
},
|
||||
async login () {
|
||||
if (!this.username) {
|
||||
alert('Email is required');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('auth:login', {
|
||||
username: this.username,
|
||||
// email: this.email,
|
||||
@@ -433,10 +428,11 @@ export default {
|
||||
await hello(network).logout();
|
||||
} catch (e) {} // eslint-disable-line
|
||||
|
||||
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
|
||||
let auth = await hello(network).login({
|
||||
scope: 'email',
|
||||
// explicitly pass the redirect url or it might redirect to /home
|
||||
redirect_uri: '', // eslint-disable-line camelcase
|
||||
redirect_uri: redirectUrl, // eslint-disable-line camelcase
|
||||
});
|
||||
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
challenge-modal(v-on:updatedChallenge='updatedChallenge')
|
||||
leave-challenge-modal(:challengeId='challenge._id')
|
||||
close-challenge-modal(:members='members', :challengeId='challenge._id')
|
||||
challenge-member-progress-modal(:memberId='progressMemberId', :challengeId='challenge._id')
|
||||
challenge-member-progress-modal(:challengeId='challenge._id')
|
||||
.col-12.col-md-8.standard-page
|
||||
.row
|
||||
.col-12.col-md-6
|
||||
@@ -231,7 +231,6 @@ export default {
|
||||
creatingTask: {},
|
||||
workingTask: {},
|
||||
taskFormPurpose: 'create',
|
||||
progressMemberId: '',
|
||||
searchTerm: '',
|
||||
memberResults: [],
|
||||
};
|
||||
@@ -345,6 +344,7 @@ export default {
|
||||
this.tasksByType[task.type].splice(index, 1);
|
||||
},
|
||||
showMemberModal () {
|
||||
// @TODO: Change these to options and add a custom event to members modal
|
||||
this.$store.state.memberModalOptions.challengeId = this.challenge._id;
|
||||
this.$store.state.memberModalOptions.groupId = 'challenge'; // @TODO: change these terrible settings
|
||||
this.$store.state.memberModalOptions.group = this.group;
|
||||
@@ -374,8 +374,9 @@ export default {
|
||||
Object.assign(this.challenge, eventData.challenge);
|
||||
},
|
||||
openMemberProgressModal (member) {
|
||||
this.progressMemberId = member._id;
|
||||
this.$root.$emit('bv::show::modal', 'challenge-member-modal');
|
||||
this.$root.$emit('habitica:challenge:member-progress', {
|
||||
progressMemberId: member._id,
|
||||
});
|
||||
},
|
||||
async exportChallengeCsv () {
|
||||
// let response = await this.$store.dispatch('challenges:exportChallengeCsv', {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template lang="pug">
|
||||
b-modal#challenge-member-modal(title="User Progress", size='lg')
|
||||
.row.award-row
|
||||
.col-12.text-center
|
||||
button.btn.btn-primary(v-once, @click='closeChallenge()') {{ $t('awardWinners') }}
|
||||
.row
|
||||
task-column.col-6(
|
||||
v-for="column in columns",
|
||||
@@ -8,12 +11,19 @@
|
||||
:taskListOverride='tasksByType[column]')
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.award-row {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import Column from '../tasks/column';
|
||||
|
||||
export default {
|
||||
props: ['challengeId', 'memberId'],
|
||||
props: ['challengeId'],
|
||||
components: {
|
||||
TaskColumn: Column,
|
||||
},
|
||||
@@ -26,8 +36,19 @@ export default {
|
||||
todo: [],
|
||||
reward: [],
|
||||
},
|
||||
memberId: '',
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:challenge:member-progress', (data) => {
|
||||
if (!data.progressMemberId) return;
|
||||
this.memberId = data.progressMemberId;
|
||||
this.$root.$emit('bv::show::modal', 'challenge-member-modal');
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica:challenge:member-progress');
|
||||
},
|
||||
watch: {
|
||||
async memberId (id) {
|
||||
if (!id) return;
|
||||
@@ -45,5 +66,14 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async closeChallenge () {
|
||||
this.challenge = await this.$store.dispatch('challenges:selectChallengeWinner', {
|
||||
challengeId: this.challengeId,
|
||||
winnerId: this.memberId,
|
||||
});
|
||||
this.$router.push('/challenges/myChallenges');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -317,14 +317,17 @@ export default {
|
||||
methods: {
|
||||
async shown () {
|
||||
this.groups = await this.$store.dispatch('guilds:getMyGuilds');
|
||||
await this.$store.dispatch('party:getParty');
|
||||
const party = this.$store.state.party.data;
|
||||
if (party._id) {
|
||||
this.groups.push({
|
||||
name: party.name,
|
||||
_id: party._id,
|
||||
privacy: 'private',
|
||||
});
|
||||
|
||||
if (this.user.party && this.user.party._id) {
|
||||
await this.$store.dispatch('party:getParty');
|
||||
const party = this.$store.state.party.data;
|
||||
if (party._id) {
|
||||
this.groups.push({
|
||||
name: party.name,
|
||||
_id: party._id,
|
||||
privacy: 'private',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.groups.push({
|
||||
|
||||
@@ -11,7 +11,7 @@ div
|
||||
)
|
||||
| {{msg.user}}
|
||||
.svg-icon(v-html="tierIcon", v-if='showShowTierStyle')
|
||||
p.time {{msg.timestamp | timeAgo}}
|
||||
p.time(v-b-tooltip="", :title="msg.timestamp | date") {{msg.timestamp | timeAgo}}
|
||||
.text(v-markdown='msg.text')
|
||||
hr
|
||||
.action(@click='like()', v-if='!inbox && msg.likes', :class='{active: msg.likes[user._id]}')
|
||||
@@ -72,6 +72,7 @@ div
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: #878190;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.text {
|
||||
@@ -165,7 +166,7 @@ export default {
|
||||
return moment(value).fromNow();
|
||||
},
|
||||
date (value) {
|
||||
// @TODO: Add user preference
|
||||
// @TODO: Vue doesn't support this so we cant user preference
|
||||
return moment(value).toDate();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -100,6 +100,7 @@ export default {
|
||||
if (!data.message || !data.groupId) return;
|
||||
this.abuseObject = data.message;
|
||||
this.groupId = data.groupId;
|
||||
this.reportComment = '';
|
||||
this.$root.$emit('bv::show::modal', 'report-flag');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
)
|
||||
.row.tasks-navigation
|
||||
.col-12.col-md-4
|
||||
h1 Group's Tasks
|
||||
h1 {{ $t('groupTasksTitle') }}
|
||||
// @TODO: Abstract to component!
|
||||
.col-12.col-md-4
|
||||
.input-group
|
||||
@@ -80,6 +80,7 @@
|
||||
:key="column",
|
||||
:taskListOverride='tasksByType[column]',
|
||||
v-on:editTask="editTask",
|
||||
v-on:loadGroupCompletedTodos="loadGroupCompletedTodos",
|
||||
:group='group',
|
||||
:searchText="searchText")
|
||||
</template>
|
||||
@@ -384,6 +385,20 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'task-modal');
|
||||
});
|
||||
},
|
||||
async loadGroupCompletedTodos () {
|
||||
const completedTodos = await this.$store.dispatch('tasks:getCompletedGroupTasks', {
|
||||
groupId: this.searchId,
|
||||
});
|
||||
|
||||
completedTodos.forEach((task) => {
|
||||
const existingTaskIndex = findIndex(this.tasksByType.todo, (todo) => {
|
||||
return todo._id === task._id;
|
||||
});
|
||||
if (existingTaskIndex === -1) {
|
||||
this.tasksByType.todo.push(task);
|
||||
}
|
||||
});
|
||||
},
|
||||
createTask (type) {
|
||||
this.taskFormPurpose = 'create';
|
||||
this.creatingTask = taskDefaults({type, text: ''});
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
:class='{"user-entry": newMessage}',
|
||||
@keydown='updateCarretPosition',
|
||||
@keyup.ctrl.enter='sendMessageShortcut()',
|
||||
@paste='disableMessageSendShortcut()'
|
||||
@paste='disableMessageSendShortcut()',
|
||||
maxlength='3000'
|
||||
)
|
||||
span {{ currentLength }} / 3000
|
||||
autocomplete(
|
||||
:text='newMessage',
|
||||
v-on:select="selectedAutocomplete",
|
||||
@@ -62,6 +64,11 @@
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentLength () {
|
||||
return this.newMessage.length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
||||
getCoord (e, text) {
|
||||
|
||||
@@ -19,19 +19,20 @@ b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
|
||||
p(v-once) {{$t('wantToJoinPartyDescription')}}
|
||||
button.btn.btn-primary(v-once, @click='shareUserId()') {{$t('shartUserId')}}
|
||||
.share-userid-options(v-if="shareUserIdShown")
|
||||
.option-item(v-once)
|
||||
.option-item(@click='copyUserId()')
|
||||
.svg-icon(v-html="icons.copy")
|
||||
input(type="text", v-model="user._id", id="userIdInput")
|
||||
| Copy User ID
|
||||
.option-item(v-once)
|
||||
//.option-item(v-once)
|
||||
.svg-icon(v-html="icons.greyBadge")
|
||||
| {{$t('lookingForGroup')}}
|
||||
.option-item(v-once)
|
||||
//.option-item(v-once)
|
||||
.svg-icon(v-html="icons.qrCode")
|
||||
| {{$t('qrCode')}}
|
||||
.option-item(v-once)
|
||||
//.option-item(v-once)
|
||||
.svg-icon.facebook(v-html="icons.facebook")
|
||||
| Facebook
|
||||
.option-item(v-once)
|
||||
//.option-item(v-once)
|
||||
.svg-icon(v-html="icons.twitter")
|
||||
| Twitter
|
||||
</template>
|
||||
@@ -108,10 +109,15 @@ b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true)
|
||||
border-radius: 2px;
|
||||
width: 220px;
|
||||
position: absolute;
|
||||
top: -8em;
|
||||
top: 9em;
|
||||
left: 4.8em;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
|
||||
#userIdInput {
|
||||
position: absolute;
|
||||
left: 1000rem;
|
||||
}
|
||||
|
||||
.option-item {
|
||||
padding: 1em;
|
||||
|
||||
@@ -183,6 +189,12 @@ export default {
|
||||
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||
this.$router.push('/party');
|
||||
},
|
||||
copyUserId () {
|
||||
const copyText = document.getElementById('userIdInput');
|
||||
copyText.select();
|
||||
document.execCommand('copy');
|
||||
alert('User ID has been copied');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#groupPrivateDescription1.icon(:title="$t('privateDescription')")
|
||||
.svg-icon(v-html='icons.information')
|
||||
b-tooltip(
|
||||
:title="$t('privateDescription')",
|
||||
:title="$t('onlyLeaderCreatesChallengesDetail')",
|
||||
target="groupPrivateDescription1",
|
||||
)
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@ div
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.removeIcon")
|
||||
span.text {{$t('removeManager2')}}
|
||||
b-dropdown-item(@click='viewProgress(member)')
|
||||
span.dropdown-icon-item
|
||||
span.text {{ $t('viewProgress') }}
|
||||
.row(v-if='isLoadMoreAvailable')
|
||||
.col-12.text-center
|
||||
button.btn.btn-secondary(@click='loadMoreMembers()') {{ $t('loadMore') }}
|
||||
@@ -475,6 +478,11 @@ export default {
|
||||
groupData.leader = member;
|
||||
this.$root.$emit('updatedGroup', groupData);
|
||||
},
|
||||
viewProgress (member) {
|
||||
this.$root.$emit('habitica:challenge:member-progress', {
|
||||
progressMemberId: member._id,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -76,10 +76,18 @@ router-link.card-link(:to="{ name: 'guild', params: { groupId: guild._id } }")
|
||||
|
||||
.gold {
|
||||
color: #fdbb5a;
|
||||
|
||||
.member-count {
|
||||
color: #fdbb5a;
|
||||
}
|
||||
}
|
||||
|
||||
.silver {
|
||||
color: #c2c2c2;
|
||||
|
||||
.member-count {
|
||||
color: #c2c2c2;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-column {
|
||||
|
||||
@@ -8,8 +8,8 @@ menu-dropdown.item-user(:right="true")
|
||||
a.dropdown-item.edit-avatar.dropdown-separated(@click='showAvatar()')
|
||||
h3 {{ user.profile.name }}
|
||||
span.small-text {{ $t('editAvatar') }}
|
||||
a.nav-link.dropdown-item.dropdown-separated(@click.prevent='showInbox()')
|
||||
| {{ $t('messages') }}
|
||||
a.nav-link.dropdown-item.dropdown-separated.d-flex.justify-content-between.align-items-center(@click.prevent='showInbox()')
|
||||
div {{ $t('messages') }}
|
||||
message-count(v-if='user.inbox.newMessages > 0', :count="user.inbox.newMessages")
|
||||
a.dropdown-item(@click='showAvatar("backgrounds", "2018")') {{ $t('backgrounds') }}
|
||||
a.dropdown-item(@click='showProfile("stats")') {{ $t('stats') }}
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
.member-stats {
|
||||
padding-left: 12px;
|
||||
padding-right: 24px;
|
||||
margin-right: 1px;
|
||||
opacity: 1;
|
||||
transition: width 0.15s ease-out;
|
||||
}
|
||||
@@ -148,6 +147,7 @@
|
||||
right: 100%;
|
||||
height: calc(100% + 18px);
|
||||
margin-top: -9px;
|
||||
margin-right: 1px;
|
||||
padding-top: 9px;
|
||||
padding-bottom: 24px;
|
||||
padding-right: 16px;
|
||||
|
||||
@@ -465,7 +465,7 @@ export default {
|
||||
this.$root.$emit('bv::show::modal', 'won-challenge');
|
||||
break;
|
||||
case 'STREAK_ACHIEVEMENT':
|
||||
this.streak(this.user.achievements.streak);
|
||||
this.text(this.user.achievements.streak);
|
||||
this.playSound('Achievement_Unlocked');
|
||||
if (!this.user.preferences.suppressModals.streak) {
|
||||
this.$root.$emit('bv::show::modal', 'streak');
|
||||
@@ -485,7 +485,9 @@ export default {
|
||||
break;
|
||||
case 'CHALLENGE_JOINED_ACHIEVEMENT':
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'joined-challenge');
|
||||
this.text(`${this.$t('achievement')}: ${this.$t('joinedChallenge')}`, () => {
|
||||
this.$root.$emit('bv::show::modal', 'joined-challenge');
|
||||
}, false);
|
||||
break;
|
||||
case 'INVITED_FRIEND_ACHIEVEMENT':
|
||||
this.playSound('Achievement_Unlocked');
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
h2.text-center Continue with Amazon
|
||||
#AmazonPayButton
|
||||
#AmazonPayWallet(v-if="amazonPayments.loggedIn", style="width: 400px; height: 228px;")
|
||||
#AmazonPayRecurring(v-if="amazonPayments.loggedIn && amazonPayments.type === 'subscription'",
|
||||
style="width: 400px; height: 140px;")
|
||||
template(v-if="amazonPayments.loggedIn && amazonPayments.type === 'subscription'")
|
||||
br
|
||||
p(v-html="$t('amazonPaymentsRecurring')")
|
||||
#AmazonPayRecurring(style="width: 400px; height: 140px;")
|
||||
.modal-footer
|
||||
.text-center
|
||||
button.btn.btn-primary(v-if="amazonPaymentsCanCheckout",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="pug">
|
||||
b-modal#send-gems(:title="title", :hide-footer="true", size='lg')
|
||||
b-modal#send-gems(:title="title", :hide-footer="true", size='lg', @hide='onHide()')
|
||||
.modal-body(v-if='userReceivingGems')
|
||||
.panel.panel-default(
|
||||
:class="gift.type === 'gems' ? 'panel-primary' : 'transparent'",
|
||||
@@ -138,6 +138,9 @@ export default {
|
||||
this.text(this.$t('sentGems'));
|
||||
this.close();
|
||||
},
|
||||
onHide () {
|
||||
this.gift.message = '';
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'send-gems');
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
transition(name="fade")
|
||||
.notification.callout.animated(:class="classes", v-if='show', @click='show = false')
|
||||
.notification.callout.animated(:class="classes", v-if='show', @click='handleOnClick()')
|
||||
.row(v-if='notification.type === "error"')
|
||||
.text.col-12
|
||||
div(v-html='notification.text')
|
||||
@@ -50,6 +50,10 @@ transition(name="fade")
|
||||
|
||||
.error {
|
||||
background-color: #f74e52;
|
||||
border-radius: 60px;
|
||||
width: 320px !important;
|
||||
padding: 10px 5px;
|
||||
margin-left: 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -142,6 +146,15 @@ export default {
|
||||
beforeDestroy () {
|
||||
clearTimeout(this.timer);
|
||||
},
|
||||
methods: {
|
||||
handleOnClick () {
|
||||
if (typeof this.notification.onClick === 'function') {
|
||||
this.notification.onClick();
|
||||
}
|
||||
|
||||
this.show = false;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
show () {
|
||||
this.$store.dispatch('snackbars:remove', this.notification);
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
<template lang="pug">
|
||||
.container-fluid
|
||||
.container-fluid(role="tablist")
|
||||
.row
|
||||
.col-12.col-md-6.offset-md-3
|
||||
h1 {{ $t('frequentlyAskedQuestions') }}
|
||||
.faq-question(v-for='(heading, index) in headings')
|
||||
h2.accordion(@click='setActivePage(heading)') {{ $t(`faqQuestion${index}`) }}
|
||||
div(v-if='pageState[heading]', v-markdown="$t('webFaqAnswer' + index, replacements)")
|
||||
h1#faq-heading {{ $t('frequentlyAskedQuestions') }}
|
||||
.faq-question(v-for='(heading, index) in headings', :key="index")
|
||||
h2(role="tab", v-b-toggle="heading", @click="handleClick($event)", variant="info") {{ $t(`faqQuestion${index}`) }}
|
||||
b-collapse(:id="heading", :visible="isVisible(heading)", accordion="faq", role="tabpanel")
|
||||
div.card-body(v-markdown="$t('webFaqAnswer' + index, replacements)")
|
||||
hr
|
||||
p(v-markdown="$t('webFaqStillNeedHelp')")
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.faq-question {
|
||||
margin-bottom: 1em;
|
||||
.card-body {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.faq-question h2 {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.faq-question .card-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.static-wrapper .faq-question h2 {
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.faq-question a {
|
||||
text-decoration: none;
|
||||
color: #4F2A93;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
@@ -25,6 +43,7 @@
|
||||
<script>
|
||||
// @TODO: env.EMAILS.TECH_ASSISTANCE_EMAIL
|
||||
const TECH_ASSISTANCE_EMAIL = 'admin@habitica.com';
|
||||
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
|
||||
export default {
|
||||
@@ -48,19 +67,15 @@
|
||||
'world-boss',
|
||||
];
|
||||
|
||||
let pageState = {};
|
||||
for (let index in headings) {
|
||||
let heading = headings[index];
|
||||
pageState[heading] = false;
|
||||
}
|
||||
const hash = window.location.hash.replace('#', '');
|
||||
|
||||
return {
|
||||
pageState,
|
||||
headings,
|
||||
replacements: {
|
||||
techAssistanceEmail: TECH_ASSISTANCE_EMAIL,
|
||||
wikiTechAssistanceEmail: `mailto:${TECH_ASSISTANCE_EMAIL}`,
|
||||
},
|
||||
visible: hash && headings.includes(hash) ? hash : null,
|
||||
// @TODO webFaqStillNeedHelp: {
|
||||
// linkStart: '[',
|
||||
// linkEnd: '](/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)',
|
||||
@@ -69,8 +84,13 @@
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
setActivePage (page) {
|
||||
this.pageState[page] = !this.pageState[page];
|
||||
isVisible (heading) {
|
||||
return this.visible && this.visible === heading;
|
||||
},
|
||||
handleClick (e) {
|
||||
if (!e) return;
|
||||
const heading = e.target.nextElementSibling.id;
|
||||
history.pushState({}, heading, `#${heading}`);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
.fast-company.svg-icon(v-html='icons.fastCompany')
|
||||
.discover.svg-icon(v-html='icons.discover')
|
||||
.container-fluid
|
||||
.seamless_stars_varied_opacity_repeat
|
||||
.row.seamless_stars_varied_opacity_repeat
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
@@ -616,11 +616,6 @@
|
||||
},
|
||||
// @TODO this is totally duplicate from the registerLogin component
|
||||
async register () {
|
||||
if (this.password !== this.passwordConfirm) {
|
||||
alert('Passwords must match');
|
||||
return;
|
||||
}
|
||||
|
||||
let groupInvite = '';
|
||||
if (this.$route.query && this.$route.query.p) {
|
||||
groupInvite = this.$route.query.p;
|
||||
@@ -663,10 +658,11 @@
|
||||
await hello(network).logout();
|
||||
} catch (e) {} // eslint-disable-line
|
||||
|
||||
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
|
||||
const auth = await hello(network).login({
|
||||
scope: 'email',
|
||||
// explicitly pass the redirect url or it might redirect to /home
|
||||
redirect_uri: '', // eslint-disable-line camelcase
|
||||
redirect_uri: redirectUrl, // eslint-disable-line camelcase
|
||||
});
|
||||
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
|
||||
@@ -67,7 +67,7 @@ export default {
|
||||
} else if (assignedUsersLength > 1 && !this.userIsAssigned) {
|
||||
return this.$t('assignedToMembers', {userCount: assignedUsersLength});
|
||||
} else if (assignedUsersLength > 1 && this.userIsAssigned) {
|
||||
return this.$t('assignedToYouAndMembers', {userCount: assignedUsersLength});
|
||||
return this.$t('assignedToYouAndMembers', {userCount: assignedUsersLength - 1});
|
||||
} else if (this.userIsAssigned) {
|
||||
return this.$t('youAreAssigned');
|
||||
} else if (assignedUsersLength === 0) {
|
||||
|
||||
@@ -27,9 +27,9 @@ export default {
|
||||
let userIsRequesting = this.task.group.approvals && this.task.group.approvals.indexOf(this.user._id) !== -1;
|
||||
|
||||
if (approvalsLength === 1 && !userIsRequesting) {
|
||||
return this.$t('youAreRequestingApproval', {userName: approvals[0].userId.profile.name});
|
||||
return this.$t('userRequestsApproval', {userName: approvals[0].userId.profile.name});
|
||||
} else if (approvalsLength > 1 && !userIsRequesting) {
|
||||
return this.$t('youAreRequestingApproval', {userCount: approvalsLength});
|
||||
return this.$t('userCountRequestsApproval', {userCount: approvalsLength});
|
||||
} else if (approvalsLength === 1 && userIsRequesting) {
|
||||
return this.$t('youAreRequestingApproval');
|
||||
}
|
||||
|
||||
@@ -81,6 +81,10 @@
|
||||
min-height: 556px;
|
||||
}
|
||||
|
||||
.sortable-tasks {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.sortable-tasks + .reward-items {
|
||||
margin-top: 16px;
|
||||
}
|
||||
@@ -358,7 +362,7 @@ export default {
|
||||
type: this.type,
|
||||
filterType: this.activeFilter.label,
|
||||
}) :
|
||||
this.taskListOverride;
|
||||
this.filterByCompleted(this.taskListOverride, this.activeFilter.label);
|
||||
|
||||
let taggedList = this.filterByTagList(filteredTaskList, this.selectedTags);
|
||||
let searchedList = this.filterBySearchText(taggedList, this.searchText);
|
||||
@@ -446,7 +450,7 @@ export default {
|
||||
|
||||
if (this.type !== 'todo') return;
|
||||
this.$root.$on('habitica::resync-requested', () => {
|
||||
if (this.activeFilters.todo.label !== 'complete2') return;
|
||||
if (this.activeFilter.label !== 'complete2') return;
|
||||
this.loadCompletedTodos(true);
|
||||
});
|
||||
},
|
||||
@@ -552,7 +556,11 @@ export default {
|
||||
activateFilter (type, filter = '') {
|
||||
// Needs a separate API call as this data may not reside in store
|
||||
if (type === 'todo' && filter === 'complete2') {
|
||||
this.loadCompletedTodos();
|
||||
if (this.group && this.group._id) {
|
||||
this.$emit('loadGroupCompletedTodos');
|
||||
} else {
|
||||
this.loadCompletedTodos();
|
||||
}
|
||||
}
|
||||
|
||||
// the only time activateFilter is called with filter==='' is when the component is first created
|
||||
@@ -590,6 +598,13 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
filterByCompleted (taskList, filter) {
|
||||
if (!taskList) return [];
|
||||
return taskList.filter(task => {
|
||||
if (filter === 'complete2') return task.completed;
|
||||
return !task.completed;
|
||||
});
|
||||
},
|
||||
filterByTagList (taskList, tagList = []) {
|
||||
let filteredTaskList = taskList;
|
||||
// filter requested tasks by tags
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="pug">
|
||||
form(v-if="task", @submit.stop.prevent="submit()")
|
||||
b-modal#task-modal(size="sm", @hidden="onClose()", @shown="focusInput()")
|
||||
b-modal#task-modal(size="sm", @hidden="onClose()", @show="handleOpen()", @shown="focusInput()")
|
||||
.task-modal-header(slot="modal-header", :class="cssClass('bg')")
|
||||
.clearfix
|
||||
h1.float-left {{ title }}
|
||||
@@ -159,11 +159,11 @@
|
||||
.option.group-options(v-if='groupId')
|
||||
.form-group.row
|
||||
label.col-12(v-once) {{ $t('assignedTo') }}
|
||||
.col-12
|
||||
.col-12.mt-2
|
||||
.category-wrap(@click="showAssignedSelect = !showAssignedSelect")
|
||||
span.category-select(v-if='assignedMembers && assignedMembers.length === 0') {{$t('none')}}
|
||||
span.category-select(v-else)
|
||||
span(v-for='memberId in assignedMembers') {{memberNamesById[memberId]}}
|
||||
span.mr-1(v-for='memberId in assignedMembers') {{memberNamesById[memberId]}}
|
||||
.category-box(v-if="showAssignedSelect")
|
||||
.container
|
||||
.row
|
||||
@@ -176,7 +176,7 @@
|
||||
label.custom-control-label(v-once, :for="`assigned-${member._id}`") {{ member.profile.name }}
|
||||
|
||||
.row
|
||||
button.btn.btn-primary(@click="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
||||
button.btn.btn-primary(@click.stop.prevent="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
||||
|
||||
.option.group-options(v-if='groupId')
|
||||
.form-group
|
||||
@@ -185,6 +185,15 @@
|
||||
:checked="requiresApproval",
|
||||
@change="updateRequiresApproval"
|
||||
)
|
||||
.form-group(v-if="task.type === 'todo'")
|
||||
label(v-once) {{ $t('sharedCompletion') }}
|
||||
b-dropdown.inline-dropdown(:text="$t(sharedCompletion)")
|
||||
b-dropdown-item(
|
||||
v-for="completionOption in ['recurringCompletion', 'singleCompletion', 'allAssignedCompletion']",
|
||||
:key="completionOption",
|
||||
@click="sharedCompletion = completionOption",
|
||||
:class="{active: sharedCompletion === completionOption}"
|
||||
) {{ $t(completionOption) }}
|
||||
|
||||
.advanced-settings(v-if="task.type !== 'reward'")
|
||||
.advanced-settings-toggle.d-flex.justify-content-between.align-items-center(@click = "showAdvancedOptions = !showAdvancedOptions")
|
||||
@@ -691,6 +700,7 @@ export default {
|
||||
calendar: calendarIcon,
|
||||
}),
|
||||
requiresApproval: false, // We can't set task.group fields so we use this field to toggle
|
||||
sharedCompletion: 'recurringCompletion',
|
||||
members: [],
|
||||
memberNamesById: {},
|
||||
assignedMembers: [],
|
||||
@@ -705,26 +715,8 @@ export default {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
async task () {
|
||||
if (this.groupId && this.task.group && this.task.group.approval && this.task.group.approval.required) {
|
||||
this.requiresApproval = true;
|
||||
}
|
||||
|
||||
if (this.groupId) {
|
||||
let members = await this.$store.dispatch('members:getGroupMembers', {
|
||||
groupId: this.groupId,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
this.members = members;
|
||||
this.members.forEach(member => {
|
||||
this.memberNamesById[member._id] = member.profile.name;
|
||||
});
|
||||
this.assignedMembers = [];
|
||||
if (this.task.group && this.task.group.assignedUsers) this.assignedMembers = this.task.group.assignedUsers;
|
||||
}
|
||||
|
||||
// @TODO: This whole component is mutating a prop and that causes issues. We need to not copy the prop similar to group modals
|
||||
if (this.task) this.checklist = clone(this.task.checklist);
|
||||
task () {
|
||||
this.syncTask();
|
||||
},
|
||||
'task.startDate' () {
|
||||
this.calculateMonthlyRepeatDays();
|
||||
@@ -813,6 +805,31 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({saveTask: 'tasks:save', destroyTask: 'tasks:destroy', createTask: 'tasks:create'}),
|
||||
async syncTask () {
|
||||
if (this.groupId && this.task.group && this.task.group.approval) {
|
||||
this.requiresApproval = this.task.group.approval.required;
|
||||
}
|
||||
|
||||
if (this.groupId) {
|
||||
let members = await this.$store.dispatch('members:getGroupMembers', {
|
||||
groupId: this.groupId,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
this.members = members;
|
||||
this.members.forEach(member => {
|
||||
this.memberNamesById[member._id] = member.profile.name;
|
||||
});
|
||||
this.assignedMembers = [];
|
||||
if (this.task.group && this.task.group.assignedUsers) this.assignedMembers = this.task.group.assignedUsers;
|
||||
if (this.task.group) this.sharedCompletion = this.task.group.sharedCompletion || 'recurringCompletion';
|
||||
}
|
||||
|
||||
// @TODO: This whole component is mutating a prop and that causes issues. We need to not copy the prop similar to group modals
|
||||
if (this.task) this.checklist = clone(this.task.checklist);
|
||||
},
|
||||
async handleOpen () {
|
||||
this.syncTask();
|
||||
},
|
||||
cssClass (suffix) {
|
||||
return this.getTaskClasses(this.task, `${this.purpose === 'edit' ? 'edit' : 'create'}-modal-${suffix}`);
|
||||
},
|
||||
@@ -886,6 +903,15 @@ export default {
|
||||
async submit () {
|
||||
if (this.newChecklistItem) this.addChecklistItem();
|
||||
|
||||
// TODO Fix up permissions on task.group so we don't have to keep doing these hacks
|
||||
if (this.groupId) {
|
||||
this.task.group.assignedUsers = this.assignedMembers;
|
||||
this.task.requiresApproval = this.requiresApproval;
|
||||
this.task.group.approval.required = this.requiresApproval;
|
||||
this.task.sharedCompletion = this.sharedCompletion;
|
||||
this.task.group.sharedCompletion = this.sharedCompletion;
|
||||
}
|
||||
|
||||
if (this.purpose === 'create') {
|
||||
if (this.challengeId) {
|
||||
this.$store.dispatch('tasks:createChallengeTasks', {
|
||||
@@ -906,19 +932,11 @@ export default {
|
||||
});
|
||||
});
|
||||
Promise.all(promises);
|
||||
|
||||
this.task.group.assignedUsers = this.assignedMembers;
|
||||
|
||||
this.$emit('taskCreated', this.task);
|
||||
} else {
|
||||
this.createTask(this.task);
|
||||
}
|
||||
} else {
|
||||
if (this.groupId) {
|
||||
this.task.group.assignedUsers = this.assignedMembers;
|
||||
this.task.requiresApproval = this.requiresApproval;
|
||||
}
|
||||
|
||||
this.saveTask(this.task);
|
||||
this.$emit('taskEdited', this.task);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ div
|
||||
button.btn.btn-secondary.gift-icon(@click='openSendGemsModal()', v-b-tooltip.hover.bottom="$t('sendGems')")
|
||||
.svg-icon.gift-icon(v-html="icons.gift")
|
||||
button.btn.btn-secondary.remove-icon(v-if='user._id !== this.userLoggedIn._id && userLoggedIn.inbox.blocks.indexOf(user._id) === -1',
|
||||
@click="blockUser()", v-b-tooltip.hover.right="$t('block')")
|
||||
@click="blockUser()", v-b-tooltip.hover.right="$t('blockWarning')")
|
||||
.svg-icon.remove-icon(v-html="icons.remove")
|
||||
button.btn.btn-secondary.positive-icon(v-if='user._id !== this.userLoggedIn._id && userLoggedIn.inbox.blocks.indexOf(user._id) !== -1',
|
||||
@click="unblockUser()", v-b-tooltip.hover.right="$t('unblock')")
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
.col-12.col-md-6
|
||||
h3(v-if='userLevel100Plus', v-once, v-html="$t('noMoreAllocate')")
|
||||
h3
|
||||
| {{$t('pointsAvailable')}}
|
||||
| {{$t('statPoints')}}
|
||||
.counter.badge(v-if='user.stats.points || userLevel100Plus')
|
||||
| {{user.stats.points}}
|
||||
.col-12.col-md-6
|
||||
@@ -135,16 +135,16 @@
|
||||
.row
|
||||
.col-12.col-md-3(v-for='(statInfo, stat) in allocateStatsList')
|
||||
.box.white.row.col-12
|
||||
.col-12.col-md-9
|
||||
.col-9
|
||||
div(:class='stat') {{ $t(stats[stat].title) }}
|
||||
.number {{ user.stats[stat] }}
|
||||
.points {{$t('pts')}}
|
||||
.col-12.col-md-3
|
||||
.col-3
|
||||
div
|
||||
.up(v-if='user.stats.points', @click='allocate(stat)')
|
||||
.up(v-if='showStatsSave', @click='allocate(stat)')
|
||||
div
|
||||
.down(@click='deallocate(stat)', v-if='user.stats.points')
|
||||
.row.save-row
|
||||
.down(v-if='showStatsSave', @click='deallocate(stat)')
|
||||
.row.save-row(v-if='showStatsSave')
|
||||
.col-12.col-md-6.offset-md-3.text-center
|
||||
button.btn.btn-primary(@click='saveAttributes()', :disabled='loading') {{ this.loading ? $t('loading') : $t('save') }}
|
||||
</template>
|
||||
@@ -238,6 +238,10 @@
|
||||
userLevel100Plus () {
|
||||
return this.user.stats.lvl >= 100;
|
||||
},
|
||||
showStatsSave () {
|
||||
const statsAreBeingUpdated = Object.values(this.statUpdates).find(stat => stat > 0);
|
||||
return Boolean(this.user.stats.points) || statsAreBeingUpdated;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getGearTitle (key) {
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
export default {
|
||||
methods: {
|
||||
makeGenericPurchase (item, type = 'buyModal', quantity = 1) {
|
||||
this.$store.dispatch('shops:genericPurchase', {
|
||||
pinType: item.pinType,
|
||||
type: item.purchaseType,
|
||||
key: item.key,
|
||||
currency: item.currency,
|
||||
quantity,
|
||||
});
|
||||
async makeGenericPurchase (item, type = 'buyModal', quantity = 1) {
|
||||
try {
|
||||
await this.$store.dispatch('shops:genericPurchase', {
|
||||
pinType: item.pinType,
|
||||
type: item.purchaseType,
|
||||
key: item.key,
|
||||
currency: item.currency,
|
||||
quantity,
|
||||
});
|
||||
} catch (e) {
|
||||
if (!e.request) {
|
||||
// axios request errors already handled by app.vue
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: '',
|
||||
text: e.message,
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
this.$root.$emit('playSound', 'Reward');
|
||||
|
||||
|
||||
@@ -56,9 +56,9 @@ export default {
|
||||
streak (val) {
|
||||
this.notify(`${val}`, 'streak');
|
||||
},
|
||||
text (val, onClick) {
|
||||
text (val, onClick, timeout) {
|
||||
if (!val) return;
|
||||
this.notify(val, 'info', null, null, onClick);
|
||||
this.notify(val, 'info', null, null, onClick, timeout);
|
||||
},
|
||||
sign (number) {
|
||||
return getSign(number);
|
||||
@@ -66,14 +66,19 @@ export default {
|
||||
round (number, nDigits) {
|
||||
return round(number, nDigits);
|
||||
},
|
||||
notify (html, type, icon, sign) {
|
||||
notify (html, type, icon, sign, onClick, timeout) {
|
||||
if (typeof timeout === 'undefined') {
|
||||
timeout = true;
|
||||
}
|
||||
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: '',
|
||||
text: html,
|
||||
type,
|
||||
icon,
|
||||
sign,
|
||||
timeout: true,
|
||||
onClick,
|
||||
timeout,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@ const PrivacyPage = () => import(/* webpackChunkName: "static" */'./components/s
|
||||
const TermsPage = () => import(/* webpackChunkName: "static" */'./components/static/terms');
|
||||
|
||||
const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'./components/auth/registerLoginReset');
|
||||
const Logout = () => import(/* webpackChunkName: "auth" */'./components/auth/logout');
|
||||
|
||||
// User Pages
|
||||
// const StatsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/stats');
|
||||
@@ -105,6 +106,7 @@ const router = new VueRouter({
|
||||
routes: [
|
||||
{ name: 'register', path: '/register', component: RegisterLoginReset, meta: {requiresLogin: false} },
|
||||
{ name: 'login', path: '/login', component: RegisterLoginReset, meta: {requiresLogin: false} },
|
||||
{ name: 'logout', path: '/logout', component: Logout },
|
||||
{ name: 'resetPassword', path: '/reset-password', component: RegisterLoginReset, meta: {requiresLogin: false} },
|
||||
{ name: 'tasks', path: '/', component: UserTasks },
|
||||
{
|
||||
|
||||
@@ -68,5 +68,5 @@ export async function socialAuth (store, params) {
|
||||
export function logout () {
|
||||
localStorage.removeItem(LOCALSTORAGE_AUTH_KEY);
|
||||
localStorage.removeItem(LOCALSTORAGE_SOCIAL_AUTH_KEY);
|
||||
window.location.href = '/logout';
|
||||
window.location.href = '/logout-server';
|
||||
}
|
||||
|
||||
@@ -176,6 +176,11 @@ export async function getGroupTasks (store, payload) {
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export async function getCompletedGroupTasks (store, payload) {
|
||||
let response = await axios.get(`/api/v4/tasks/group/${payload.groupId}?type=completedTodos`);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export async function createGroupTasks (store, payload) {
|
||||
let response = await axios.post(`/api/v4/tasks/group/${payload.groupId}`, payload.tasks);
|
||||
return response.data.data;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"achievement": "Achievement",
|
||||
"share": "Споделяне",
|
||||
"onwards": "Напред!",
|
||||
"levelup": "Изпълнявайки целите си в истинския живот, Вие качихте ниво и здравето Ви беше запълнено!",
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
"locationRequired": "Мястото на предизвикателството е задължително („Добавяне в“)",
|
||||
"categoiresRequired": "Задължително е да бъде избрана поне една категория",
|
||||
"viewProgressOf": "Преглед на напредъка на",
|
||||
"viewProgress": "Преглед на напредъка",
|
||||
"selectMember": "Изберете член",
|
||||
"confirmKeepChallengeTasks": "Искате ли да задържите задачите от предизвикателството?",
|
||||
"selectParticipant": "Изберете участник"
|
||||
|
||||
@@ -219,6 +219,6 @@
|
||||
"bodyAccess": "Аксесоар за тяло",
|
||||
"mainHand": "Основна ръка",
|
||||
"offHand": "Страничен",
|
||||
"pointsAvailable": "Налични точки",
|
||||
"statPoints": "Stat Points",
|
||||
"pts": "точки"
|
||||
}
|
||||
@@ -170,6 +170,9 @@
|
||||
"questEggSquirrelText": "Катерица",
|
||||
"questEggSquirrelMountText": "Катерица",
|
||||
"questEggSquirrelAdjective": "рунтава",
|
||||
"questEggSeaSerpentText": "Морски змей",
|
||||
"questEggSeaSerpentMountText": "Морски змей",
|
||||
"questEggSeaSerpentAdjective": "блещукащ",
|
||||
"eggNotes": "Намерете излюпваща отвара, която да излеете върху това яйце и от него ще се излюпи <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
|
||||
"hatchingPotionBase": "Нормален цвят",
|
||||
"hatchingPotionWhite": "Бял цвят",
|
||||
|
||||
@@ -260,12 +260,12 @@
|
||||
"weaponSpecialSpring2018HealerNotes": "Камъните в този жезъл ще концентрира силите Ви, когато изпълнявате лечебни заклинания! Увеличава интелигентността с <%= int %>. Ограничена серия: Пролетна екипировка 2018 г.",
|
||||
"weaponSpecialSummer2018RogueText": "Въдица",
|
||||
"weaponSpecialSummer2018RogueNotes": "Можете да държите по две от тези леки и практически неразрушими въдици, за да удвоите улова си. Увеличава силата с <%= str %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"weaponSpecialSummer2018WarriorText": "Betta Fish Spear",
|
||||
"weaponSpecialSummer2018WarriorNotes": "Mighty enough for battle, elegant enough for ceremony, this exquisitely crafted spear shows you will protect your home surf no matter what! Increases Strength by <%= str %>. Limited Edition 2018 Summer Gear.",
|
||||
"weaponSpecialSummer2018MageText": "Lionfish Fin Rays",
|
||||
"weaponSpecialSummer2018MageNotes": "Underwater, magic based on fire, ice, or electricity can prove hazardous to the Mage wielding it. Conjuring poisonous spines, however, works brilliantly! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2018 Summer Gear.",
|
||||
"weaponSpecialSummer2018HealerText": "Merfolk Monarch Trident",
|
||||
"weaponSpecialSummer2018HealerNotes": "With a benevolent gesture, you command healing water to flow through your dominions in waves. Increases Intelligence by <%= int %>. Limited Edition 2018 Summer Gear.",
|
||||
"weaponSpecialSummer2018WarriorText": "Копие на сиамска бойна риба",
|
||||
"weaponSpecialSummer2018WarriorNotes": "Достатъчно здраво за битки и достатъчно изтънчено за церемонии – това специално изработено копие показва, че ще защитите дома си на всяка цена! Увеличава силата с <%= str %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"weaponSpecialSummer2018MageText": "Лъчи-перки на лъвска риба",
|
||||
"weaponSpecialSummer2018MageNotes": "Под водата заклинанията, основаващи се на огън, лед или електричество, може да бъдат опасни за Магьосника. Измагьосването на отровни шипове, обаче, работи безпроблемно! Увеличава интелигентността с <%= int %> и усета с <%= per %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"weaponSpecialSummer2018HealerText": "Царски русалски тризъбец",
|
||||
"weaponSpecialSummer2018HealerNotes": "С лек благосклонен жест насочвате лечебна вода на вълни през владенията си. Увеличава интелигентността с <%= int %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"weaponMystery201411Text": "Вилица на изобилието",
|
||||
"weaponMystery201411Notes": "Наръгайте враговете си или си боцнете от любимата храна — тази универсална вилица може всичко! не променя показателите. Предмет за абонати: ноември 2014 г.",
|
||||
"weaponMystery201502Text": "Блестящият крилат скиптър на любовта и истината",
|
||||
@@ -346,8 +346,8 @@
|
||||
"weaponArmoireCobblersHammerNotes": "Това е специален чук за обработка на кожа. Но може да се използва и срещу червените ежедневни задачи. Увеличава якостта и силата с по <%= attrs %>. Омагьосан гардероб: Обущарски комплект (предмет 2 от 3).",
|
||||
"weaponArmoireGlassblowersBlowpipeText": "Тръба за духане на стъклодухач",
|
||||
"weaponArmoireGlassblowersBlowpipeNotes": "Използвайте тази тръба, за да изваете разтопеното стъкло и да го превърнете в красиви вази, орнаменти и други интересни неща. Увеличава силата с <%= str %>. Омагьосан гардероб: комплект „Стъклодухач“ (предмет 1 от 4).",
|
||||
"weaponArmoirePoisonedGobletText": "Poisoned Goblet",
|
||||
"weaponArmoirePoisonedGobletNotes": "Use this to build your resistance to iocane powder and other inconceivably dangerous poisons. Increases Intelligence by <%= int %>. Enchanted Armoire: Piratical Princess Set (Item 3 of 4).",
|
||||
"weaponArmoirePoisonedGobletText": "Отровен бокал",
|
||||
"weaponArmoirePoisonedGobletNotes": "Използвайте това, за да изградите имунитет срещу различни невъобразимо опасни отрови. Увеличава интелигентността с <%= int %>. Омагьосан гардероб: комплект „Пиратска принцеса“ (предмет 3 от 4).",
|
||||
"armor": "броня",
|
||||
"armorCapitalized": "Броня",
|
||||
"armorBase0Text": "Обикновени дрехи",
|
||||
@@ -582,14 +582,14 @@
|
||||
"armorSpecialSpring2018MageNotes": "Заклинанията Ви ще се подобрят, ако носите тези меки копринени цветчета. Увеличава интелигентността с <%= int %>. Ограничена серия: Пролетна екипировка 2018 г.",
|
||||
"armorSpecialSpring2018HealerText": "Гранатена броня",
|
||||
"armorSpecialSpring2018HealerNotes": "Нека тази светла броня даде на сърцето Ви силата да лекувате. Увеличава якостта с <%= con %>. Ограничена серия: Пролетна екипировка 2018 г.",
|
||||
"armorSpecialSummer2018RogueText": "Pocket Fishing Vest",
|
||||
"armorSpecialSummer2018RogueNotes": "Bobbers? Boxes of hooks? Spare line? Lockpicks? Smoke bombs? Whatever you need on hand for your summer fishing getaway, there's a pocket for it! Increases Perception by <%= per %>. Limited Edition 2018 Summer Gear.",
|
||||
"armorSpecialSummer2018WarriorText": "Betta Tail Armor",
|
||||
"armorSpecialSummer2018WarriorNotes": "Dazzle onlookers with whorls of magnificent color as you spin and dart through the water. How could any opponent dare strike at this beauty? Increases Constitution by <%= con %>. Limited Edition 2018 Summer Gear.",
|
||||
"armorSpecialSummer2018MageText": "Lionfish Scale Hauberk",
|
||||
"armorSpecialSummer2018MageNotes": "Venom magic has a reputation for subtlety. Not so this colorful armor, whose message is clear to beast and task alike: watch out! Increases Intelligence by <%= int %>. Limited Edition 2018 Summer Gear.",
|
||||
"armorSpecialSummer2018HealerText": "Merfolk Monarch Robes",
|
||||
"armorSpecialSummer2018HealerNotes": "These cerulean vestments reveal that you have land-walking feet... well. Not even a monarch can be expected to be perfect. Increases Constitution by <%= con %>. Limited Edition 2018 Summer Gear.",
|
||||
"armorSpecialSummer2018RogueText": "Джобен рибарски елек",
|
||||
"armorSpecialSummer2018RogueNotes": "Плувки? Кутии с кукички? Резервна корда? Инструменти за разбиване на ключалки? Димни бомби? Каквото и да Ви потрябва за тазгодишния риболовен сезон, има джоб за него! Увеличава усета с <%= per %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"armorSpecialSummer2018WarriorText": "Опашка на сиамска бойна риба",
|
||||
"armorSpecialSummer2018WarriorNotes": "Заслепете зяпачите с разноцветни спирали, докато се въртите и стрелкате през водата. Как може някой враг изобщо да се осмели да нарани такава красота? Увеличава якостта с <%= con %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"armorSpecialSummer2018MageText": "Люспена броня на лъвска риба",
|
||||
"armorSpecialSummer2018MageNotes": "Заклинанията за отравяне обикновено се правят незабележимо. Не и с тази цветна броня, с която всичко се казва ясно: внимавайте! Увеличава интелигентността с <%= int %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"armorSpecialSummer2018HealerText": "Царски русалски одежди",
|
||||
"armorSpecialSummer2018HealerNotes": "Тези небесно сини одежди показват, че имате… крака за ходене по земята. Дори и царете не са перфектни. Увеличава якостта с <%= con %>. Ограничена серия: Лятна екипировка 2017 г.",
|
||||
"armorMystery201402Text": "Одежди на вестоносец",
|
||||
"armorMystery201402Notes": "Блестящи и здрави, тези одежди имат много джобове за носене на писма. Не променя показателите. Предмет за абонати: февруари 2014 г.",
|
||||
"armorMystery201403Text": "Броня на горски бродник",
|
||||
@@ -654,8 +654,10 @@
|
||||
"armorMystery201712Notes": "Топлината и светлината произведени от тази вълшебна броня ще стоплят сърцето Ви, но няма да изгорят кожата Ви! Не променя показателите. Предмет за абонати: декември 2017 г.",
|
||||
"armorMystery201802Text": "Броня на любовна буболечка",
|
||||
"armorMystery201802Notes": "Тази лъскава броня отразява силата на сърцето Ви и я разпръсква към хабитиканците наоколо, които имат нужда от окуражаване! Не променя показателите. Предмет за абонати: февруари 2018 г.",
|
||||
"armorMystery201806Text": "Alluring Anglerfish Tail",
|
||||
"armorMystery201806Notes": "This sinuous tail features glowing spots to light your way through the deep. Confers no benefit. June 2018 Subscriber Item.",
|
||||
"armorMystery201806Text": "Опашка на морски дявол",
|
||||
"armorMystery201806Notes": "По тази игрива опашка има светещи петна, които ще осветят пътя Ви в дълбините. Не променя показателите. Предмет за абонати: юни 2018 г.",
|
||||
"armorMystery201807Text": "Опашка на морски змей",
|
||||
"armorMystery201807Notes": "Тази могъща опашка ще Ви изстреля с невероятна скорост през морските дълбини! Не променя показателите. Предмет за абонати: юли 2018 г.",
|
||||
"armorMystery301404Text": "Изтънчен костюм",
|
||||
"armorMystery301404Notes": "Спретнат и елегантен! Не променя показателите. Предмет за абонати: февруари 3015 г.",
|
||||
"armorMystery301703Text": "Изтънчена паунова рокля",
|
||||
@@ -746,8 +748,8 @@
|
||||
"armorArmoireGlassblowersCoverallsNotes": "Този гащеризон ще Ви защити, когато създавате шедьоври от разтопено стъкло. Увеличава якостта с <%= con %>. Омагьосан гардероб: комплект „Стъклодухач“ (предмет 2 от 4).",
|
||||
"armorArmoireBluePartyDressText": "Синя бална рокля",
|
||||
"armorArmoireBluePartyDressNotes": "Вие имате концентрация, сила и ум, и сте в крак с модата! Увеличава усета, силата и якостта с по <%= attrs %>. Омагьосан гардероб: комплект „Синя панделка“ (предмет 2 от 2).",
|
||||
"armorArmoirePiraticalPrincessGownText": "Piratical Princess Gown",
|
||||
"armorArmoirePiraticalPrincessGownNotes": "This luxuriant garment has many pockets for concealing weapons and loot! Increases Perception by <%= per %>. Enchanted Armoire: Piratical Princess Set (Item 2 of 4).",
|
||||
"armorArmoirePiraticalPrincessGownText": "Рокля на пиратска принцеса",
|
||||
"armorArmoirePiraticalPrincessGownNotes": "В тази луксозна дреха има много джобове за криене на оръжия и плячка! Увеличава усета с <%= per %>. Омагьосан гардероб: комплект „Пиратска принцеса“ (предмет 2 от 4).",
|
||||
"headgear": "шлем",
|
||||
"headgearCapitalized": "Защита за главата",
|
||||
"headBase0Text": "Няма защита за главата",
|
||||
@@ -984,12 +986,12 @@
|
||||
"headSpecialSpring2018HealerNotes": "Полираните скъпоценни камъни по тази диадема ще увеличат мисловните Ви сили. Увеличава интелигентността с <%= int %>. Ограничена серия: Пролетна екипировка 2018 г.",
|
||||
"headSpecialSummer2018RogueText": "Рибарска шапка за слънце",
|
||||
"headSpecialSummer2018RogueNotes": "Осигурява комфорт и защита от жаркото лятно слънце над водата. Това е особено важно, ако сте свикнали да се промъквате в сенките! Увеличава силата с <%= per %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"headSpecialSummer2018WarriorText": "Betta Fish Barbute",
|
||||
"headSpecialSummer2018WarriorNotes": "Show everyone you're the alpha betta with this flamboyant helm! Increases Strength by <%= str %>. Limited Edition 2018 Summer Gear.",
|
||||
"headSpecialSummer2018MageText": "Lionfish Crest",
|
||||
"headSpecialSummer2018MageNotes": "Glare dolorously upon anyone who dares say you look like a “tastyfish”. Increases Perception by <%= per %>. Limited Edition 2018 Summer Gear.",
|
||||
"headSpecialSummer2018HealerText": "Merfolk Monarch Crown",
|
||||
"headSpecialSummer2018HealerNotes": "Adorned with aquamarine, this finned diadem marks leadership of folk, fish, and those who are a bit of both! Increases Intelligence by <%= int %>. Limited Edition 2018 Summer Gear.",
|
||||
"headSpecialSummer2018WarriorText": "Шлем на сиамска бойна риба",
|
||||
"headSpecialSummer2018WarriorNotes": "Покажете на всички, че не бива да се закачат с Вас – с този пищен шлем! Увеличава силата с <%= str %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"headSpecialSummer2018MageText": "Гребен на лъвска риба",
|
||||
"headSpecialSummer2018MageNotes": "Пригответе убийствения си поглед, за всеки, който се осмели да каже, че изглеждате „сладко“. Увеличава усета с <%= per %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"headSpecialSummer2018HealerText": "Царска русалска корона",
|
||||
"headSpecialSummer2018HealerNotes": "Украсена със зеленикаво-сини оттенъци, тази диадема с перки показва господарството Ви над хората, рибите и онези, които са по малко от двете! Увеличава интелигентността с <%= int %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"headSpecialGaymerxText": "Боен шлем с цветовете на дъгата",
|
||||
"headSpecialGaymerxNotes": "В чест на конференцията GaymerX, този специален шлем е оцветен с шарка на дъга! GaymerX е игрално изложение в чест на ЛГБТ културата и игрите и е отворено за всички.",
|
||||
"headMystery201402Text": "Крилат шлем",
|
||||
@@ -1060,8 +1062,10 @@
|
||||
"headMystery201803Notes": "Въпреки че изглежда като обикновено украшение, можете да накарате крилата на тази диадема да махат и да… получите още по-хубава украса! Не променя показателите. Предмет за абонати: март 2018 г.",
|
||||
"headMystery201805Text": "Феноменален паунов шлем",
|
||||
"headMystery201805Notes": "С този шлем ще се превърнете в най-гордата и най-красивата (а сигурно и най-шумната) птица наоколо. Не променя показателите. Предмет за абонати: май 2018 г.",
|
||||
"headMystery201806Text": "Alluring Anglerfish Helm",
|
||||
"headMystery201806Notes": "The mesmerizing light atop this helm will call all the creatures of the sea to your side. We urge you to use your glowy powers of attraction for good! Confers no benefit. June 2018 Subscriber Item.",
|
||||
"headMystery201806Text": "Шлем на морски дявол",
|
||||
"headMystery201806Notes": "Хипнотизиращата светлина, закачена отгоре на този шлем, ще привика всички морски същества на Ваша страна. Не променя показателите. Предмет за абонати: февруари 2018 г.",
|
||||
"headMystery201807Text": "Шлем на морски змей",
|
||||
"headMystery201807Notes": "Здравите люспи на този шлем ще Ви защитят от всеки морски враг. Не променя показателите. Предмет за абонати: юли 2018 г.",
|
||||
"headMystery301404Text": "Украсен цилиндър",
|
||||
"headMystery301404Notes": "Украсен цилиндър за най-изтънчените и високопоставени членове на обществото. Не променя показателите. Предмет за абонати: януари 3015 г.",
|
||||
"headMystery301405Text": "Обикновен цилиндър",
|
||||
@@ -1162,8 +1166,8 @@
|
||||
"headArmoireBigWigNotes": "Някои напудрени перуки придават авторитет, но тази е само за смях! Увеличава силата с <%= str %>. Омагьосан гардероб: независим предмет.",
|
||||
"headArmoireGlassblowersHatText": "Шапка на стъклодухач",
|
||||
"headArmoireGlassblowersHatNotes": "Тази шапка просто си отива с останалото защитно облекло на стъклодухач! Увеличава усета с <%= per %>. Омагьосан гардероб: комплект „Стъклодухач“ (предмет 3 от 4).",
|
||||
"headArmoirePiraticalPrincessHeaddressText": "Piratical Princess Headdress",
|
||||
"headArmoirePiraticalPrincessHeaddressNotes": "Fancy buccaneers are known for their fancy headwear! Increases Perception and Intelligence by <%= attrs %> each. Enchanted Armoire: Piratical Princess Set (Item 1 of 4).",
|
||||
"headArmoirePiraticalPrincessHeaddressText": "Украса за глава на пиратска принцеса",
|
||||
"headArmoirePiraticalPrincessHeaddressNotes": "Пиратите са известни с причудливите неща, които носят на главите си! Увеличава усета и интелигентността с по <%= attrs %>. Омагьосан гардероб: комплект „Пиратска принцеса“ (предмет 1 от 4).",
|
||||
"offhand": "страничен предмет",
|
||||
"offhandCapitalized": "Страничен предмет",
|
||||
"shieldBase0Text": "Няма страничен предмет",
|
||||
@@ -1314,10 +1318,10 @@
|
||||
"shieldSpecialSpring2018WarriorNotes": "Този здрав щит свети с величието на най-чистата светлина. Увеличава якостта с <%= con %>. Ограничена серия: Пролетна екипировка 2018 г.",
|
||||
"shieldSpecialSpring2018HealerText": "Гранатен щит",
|
||||
"shieldSpecialSpring2018HealerNotes": "Въпреки вида си, този гранатен щит е доста здрав! Увеличава якостта с <%= con %>. Ограничена серия: Пролетна екипировка 2018 г.",
|
||||
"shieldSpecialSummer2018WarriorText": "Betta Skull Shield",
|
||||
"shieldSpecialSummer2018WarriorNotes": "Fashioned from stone, this fearsome skull-styled shield strikes fear into fish foes while rallying your Skeleton pets and mounts. Increases Constitution by <%= con %>. Limited Edition 2018 Summer Gear.",
|
||||
"shieldSpecialSummer2018HealerText": "Merfolk Monarch Emblem",
|
||||
"shieldSpecialSummer2018HealerNotes": "This shield can produce a dome of air for the benefit of land-dwelling visitors to your watery realm. Increases Constitution by <%= con %>. Limited Edition 2018 Summer Gear.",
|
||||
"shieldSpecialSummer2018WarriorText": "Щит от череп на сиамска бойна риба",
|
||||
"shieldSpecialSummer2018WarriorNotes": "Изработен от камък, този страховит щит във формата на череп ще всее ужас в рибните неприятели, като същевременно ще сплоти скелетните Ви любимци и превози. Увеличава якостта с <%= con %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"shieldSpecialSummer2018HealerText": "Царски русалски герб",
|
||||
"shieldSpecialSummer2018HealerNotes": "Този щит може да създаде мехур от въздух за земните гости на подводното Ви царство. Увеличава якостта с <%= con %>. Ограничена серия: Лятна екипировка 2018 г.",
|
||||
"shieldMystery201601Text": "Решителен убиец",
|
||||
"shieldMystery201601Notes": "Това острие може да отблъсне всички разсейващи Ви неща. Не променя показателите. Предмет за абонати: януари 2016 г.",
|
||||
"shieldMystery201701Text": "Спиращ времето щит",
|
||||
@@ -1378,8 +1382,8 @@
|
||||
"shieldArmoireFancyShoeNotes": "Много специална обувка, по която работите. Подходяща за царски величия! Увеличава интелигентността и усета с по <%= attrs %>. Омагьосан гардероб: Обущарски комплект (предмет 3 от 3).",
|
||||
"shieldArmoireFancyBlownGlassVaseText": "Изящна ваза от стъкло",
|
||||
"shieldArmoireFancyBlownGlassVaseNotes": "Каква страхотна ваза сътворихте! Какво има вътре? Увеличава интелигентността с <%= int %>. Омагьосан гардероб: комплект „Стъклодухач“ (предмет 4 от 4).",
|
||||
"shieldArmoirePiraticalSkullShieldText": "Piratical Skull Shield",
|
||||
"shieldArmoirePiraticalSkullShieldNotes": "This enchanted shield will whisper the secret locations of your enemies' treasures- listen closely! Increases Perception and Intelligence by <%= attrs %> each. Enchanted Armoire: Piratical Princess Set (Item 4 of 4).",
|
||||
"shieldArmoirePiraticalSkullShieldText": "Пиратски щит от череп",
|
||||
"shieldArmoirePiraticalSkullShieldNotes": "Този омагьосан щит шепне къде са местата, където враговете Ви са заровили тайните си съкровища. Слушайте внимателно! Увеличава усета и интелигентността с по <%= attrs %>. Омагьосан гардероб: комплект „Пиратска принцеса“ (предмет 4 от 4).",
|
||||
"back": "Аксесоар за гръб",
|
||||
"backCapitalized": "Аксесоар за гръб",
|
||||
"backBase0Text": "Няма аксесоар за гръб",
|
||||
|
||||
@@ -134,13 +134,14 @@
|
||||
"PMPlaceholderDescription": "Изберете разговор отляво",
|
||||
"PMPlaceholderTitleRevoked": "Привилегиите Ви в чата Ви бяха отнети",
|
||||
"PMPlaceholderDescriptionRevoked": "Не можете да изпращате лични съобщения, тъй като привилегиите Ви в чата Ви бяха отнети. Ако имате въпроси или притеснения относно това, моля, пишете на <a href=\"mailto:admin@habitica.com\">admin@habitica.com</a>, за да ги обсъдите с екипа.",
|
||||
"PMReceive": "Receive Private Messages",
|
||||
"PMEnabledOptPopoverText": "Private Messages are enabled. Users can contact you via your profile.",
|
||||
"PMDisabledOptPopoverText": "Private Messages are disabled. Enable this option to allow users to contact you via your profile.",
|
||||
"PMReceive": "Получаване на лични съобщения",
|
||||
"PMEnabledOptPopoverText": "Личните съобщения са включени. Потребителите могат да се свържат с Вас чрез профила Ви.",
|
||||
"PMDisabledOptPopoverText": "Личните съобщения са изключени. Включете ги, ако искате потребителите да могат да се свързват с Вас чрез профила Ви.",
|
||||
"PMDisabledCaptionTitle": "Личните съобщения са забранени",
|
||||
"PMDisabledCaptionText": "You can still send messages, but no one can send them to you.",
|
||||
"PMDisabledCaptionText": "Вие можете да изпращате съобщения, но никой няма да може да изпраща на Вас.",
|
||||
"block": "Блокиране",
|
||||
"unblock": "Отблокиране",
|
||||
"blockWarning": "Block - This will have no effect if the player is a moderator now or becomes a moderator in future.",
|
||||
"pm-reply": "Изпращане на отговор",
|
||||
"inbox": "Входяща поща",
|
||||
"messageRequired": "Нужно е съобщение.",
|
||||
@@ -255,7 +256,7 @@
|
||||
"confirmApproval": "Наистина ли искате да одобрите тази задача?",
|
||||
"confirmNeedsWork": "Наистина ли искате да отбележите, че тази задача се нуждае от още работа?",
|
||||
"userRequestsApproval": "<%= userName %> иска одобрение",
|
||||
"userCountRequestsApproval": "<%= userCount %> потребители искат одобрение",
|
||||
"userCountRequestsApproval": "<%= userCount %> членове искат одобрение",
|
||||
"youAreRequestingApproval": "Вие искате одобрение",
|
||||
"chatPrivilegesRevoked": "Не можете да направите това, защото привилегиите Ви в чата са Ви били отнети.",
|
||||
"cannotCreatePublicGuildWhenMuted": "Не можете да създадете обществена гилдия, защото привилегиите Ви в чата са Ви били отнети.",
|
||||
@@ -380,6 +381,7 @@
|
||||
"bronzeTier": "Бронзово ниво",
|
||||
"privacySettings": "Настройки за поверителността",
|
||||
"onlyLeaderCreatesChallenges": "Само водачът може да създава предизвикателства",
|
||||
"onlyLeaderCreatesChallengesDetail": "Ако това е избрано, обикновените членове на групата няма да могат да създават предизвикателства.",
|
||||
"privateGuild": "Частна гилдия",
|
||||
"charactersRemaining": "Оставащи знаци: <%= characters %>",
|
||||
"guildSummary": "Резюме",
|
||||
@@ -467,5 +469,9 @@
|
||||
"howToRequireApprovalDesc2": "Водачите и управителите на групата могат да одобряват завършените задачи направо от дъската със задачи или от областта за известия.",
|
||||
"whatIsGroupManager": "Какво е „управител на групата“?",
|
||||
"whatIsGroupManagerDesc": "Управител на групата е такъв потребител, който няма достъп до информацията за таксуването на групата, но може да създава, назначава и одобрява споделени задачи за членовете на групата. Можете да определите кои са управителите на групата от списъка с членовете.",
|
||||
"goToTaskBoard": "Към дъската със задачи"
|
||||
"goToTaskBoard": "Към дъската със задачи",
|
||||
"sharedCompletion": "Shared Completion",
|
||||
"recurringCompletion": "None - Group task does not complete",
|
||||
"singleCompletion": "Single - Completes when any assigned user finishes",
|
||||
"allAssignedCompletion": "All - Completes when all assigned users finish"
|
||||
}
|
||||
@@ -121,16 +121,16 @@
|
||||
"spring2018TulipMageSet": "Магьосник на лалето (магьосник)",
|
||||
"spring2018GarnetHealerSet": "Гранатен лечител (лечител)",
|
||||
"spring2018DucklingRogueSet": "Патешки мошеник (мошеник)",
|
||||
"summer2018BettaFishWarriorSet": "Betta Fish Warrior (Warrior)",
|
||||
"summer2018LionfishMageSet": "Lionfish Mage (Mage)",
|
||||
"summer2018MerfolkMonarchSet": "Merfolk Monarch (Healer)",
|
||||
"summer2018FisherRogueSet": "Fisher-Rogue (Rogue)",
|
||||
"summer2018BettaFishWarriorSet": "Сиамски рибен воин (воин)",
|
||||
"summer2018LionfishMageSet": "Лъвски рибен магьосник (магьосник)",
|
||||
"summer2018MerfolkMonarchSet": "Цар на русалките (лечител)",
|
||||
"summer2018FisherRogueSet": "Рибар-мошеник (мошеник)",
|
||||
"eventAvailability": "Налично за купуване до <%= date(locale) %>.",
|
||||
"dateEndMarch": "30 април",
|
||||
"dateEndApril": "19 април",
|
||||
"dateEndMay": "31 май",
|
||||
"dateEndJune": "14 юни",
|
||||
"dateEndJuly": "July 31",
|
||||
"dateEndJuly": "31 юли",
|
||||
"dateEndAugust": "31 август",
|
||||
"dateEndOctober": "31 октомври",
|
||||
"dateEndNovember": "30 ноември",
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
"guildQuestsNotSupported": "Гилдиите не могат да бъдат канени в мисии.",
|
||||
"questNotOwned": "Не притежавате този свитък с мисия.",
|
||||
"questNotGoldPurchasable": "Мисията „<%= key %>“ не може да бъде купена със злато.",
|
||||
"questNotGemPurchasable": "Quest \"<%= key %>\" is not a Gem-purchasable quest.",
|
||||
"questNotGemPurchasable": "Мисията „<%= key %>“ не може да бъде купена с диаманти.",
|
||||
"questLevelTooHigh": "Трябва да бъдете ниво <%= level %>, за да започнете тази мисия.",
|
||||
"questAlreadyUnderway": "Групата Ви е вече изпълнява мисия. Опитайте отново, когато текущата мисия приключи.",
|
||||
"questAlreadyAccepted": "Вече сте приели поканата за мисията.",
|
||||
|
||||
@@ -604,5 +604,11 @@
|
||||
"cuddleBuddiesText": "Пакет мисии „Пухкави приятелчета“",
|
||||
"cuddleBuddiesNotes": "Съдържа: „Зайчето-убиец“, „Нечестивият пор“ и „Бандата на морските свинчета“. Наличен до 31 май.",
|
||||
"aquaticAmigosText": "Пакет мисии „Водни дружки“",
|
||||
"aquaticAmigosNotes": "Съдържа: „Вълшебният саламандър“, „Кракенът на незавършеността“ и „Зовът на Октотулу“. Наличен до 30 юни."
|
||||
"aquaticAmigosNotes": "Съдържа: „Вълшебният саламандър“, „Кракенът на незавършеността“ и „Зовът на Октотулу“. Наличен до 30 юни.",
|
||||
"questSeaSerpentText": "Опасност в дълбините: Нападението на морския змей!",
|
||||
"questSeaSerpentNotes": "С всичките си серии се чувстваш като късметлия – и сега е идеално време за посещение на хиподрума за морски кончета. Качваш се на подводницата от пристанище Усърдие и се настаняваш удобно за пътуването до Мудноград, но едва се потапяте, когато нещо удря подводницата и запраща всички пътници на пода. „Какво става“ — крещи @AriesFaries?<br><br>Поглеждаш през близкия люк и ужас виждаш как покрай него преминава стена от блестящи люспи. „Морски змей“ — обявява капитан @Witticaster по вътрешното радио! — „Пригответе се, връща се!“ Сграбчвайки подлакътниците на седалката си, виждаш незавършените си задачи да преминават пред погледа ти. „Може би, ако работим заедно и ги завършим“, мислиш си, „ще прогоним чудовището“.",
|
||||
"questSeaSerpentCompletion": "Сломен от усърдието ви, морският змей отстъпва и изчезва в дълбините. Когато пристигаш в Мудноград, най-после си отдъхваш, и забелязваш, че @*~Seraphina~ се приближава с три яйца. „Ето, вземи ги“ — казва тя. — „Явно знаеш как да се оправяш с морските змейове!“ Когато вземаш яйцата, отново се заричаш да бъдеш по-сериозен със задачите си, за да не се повтори тази случка.",
|
||||
"questSeaSerpentBoss": "Могъщият морски змей",
|
||||
"questSeaSerpentDropSeaSerpentEgg": "Морски змей (яйце)",
|
||||
"questSeaSerpentUnlockText": "Отключва възможността за купуване на яйца на морски змей от пазара."
|
||||
}
|
||||
@@ -183,6 +183,7 @@
|
||||
"mysticHourglassesTooltip": "Тайнствени пясъчни часовници",
|
||||
"paypal": "PayPal",
|
||||
"amazonPayments": "Плащания през Amazon",
|
||||
"amazonPaymentsRecurring": "Ticking the checkbox below is necessary for your subscription to be created. It allows your Amazon account to be used for ongoing payments for <strong>this</strong> subscription. It will not cause your Amazon account to be automatically used for any future purchases.",
|
||||
"timezone": "Часови пояс",
|
||||
"timezoneUTC": "Хабитика използва часовия пояс, зададен на Вашия компютър, който е: <strong><%= utc %></strong>",
|
||||
"timezoneInfo": "Ако този часови пояс е грешен, първо, презаредете страницата чрез бутона за презареждане или опресняване на браузъра си, за да е сигурно, че Хабитика разполага с най-актуалните данни. Ако все още има грешка, настройте часовия пояс на компютъра си и след това презаредете тази страница отново.<br><br> <strong>Ако използвате Хабитика на други компютри или мобилни устройства, часовият пояс трябва да бъде еднакъв на всички тях.</strong> Ако ежедневните Ви задачи се подновяват в грешно време, повторете тази проверка на всичките си останали компютри и чрез браузъра на всички свои мобилни устройства.",
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
"mysterySet201803": "Комплект на смелото водно конче",
|
||||
"mysterySet201804": "Комплект на изтупаната катерица",
|
||||
"mysterySet201805": "Феноменален паунов комплект",
|
||||
"mysterySet201806": "Alluring Anglerfish Set",
|
||||
"mysterySet201806": "Комплект на морския дявол",
|
||||
"mysterySet301404": "Стандартен изтънчен комплект",
|
||||
"mysterySet301405": "Комплект изтънчени принадлежности",
|
||||
"mysterySet301703": "Изтънчен паунов комплект",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"achievement": "Achievement",
|
||||
"share": "Sdílet",
|
||||
"onwards": "Kupředu!",
|
||||
"levelup": "Díky dosažení tvých cílů v reálném životě jsi se dostal na vyšší úroveň, a jsi díky tomu plně uzdraven!",
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
"locationRequired": "Location of challenge is required ('Add to')",
|
||||
"categoiresRequired": "Musí být vybrána jedna nebo více kategorií",
|
||||
"viewProgressOf": "Zobrazit pokrok",
|
||||
"viewProgress": "View Progress",
|
||||
"selectMember": "Vyber člena",
|
||||
"confirmKeepChallengeTasks": "Chceš ponechat úkoly z výzvy?",
|
||||
"selectParticipant": "Zvol účastníka"
|
||||
|
||||
@@ -219,6 +219,6 @@
|
||||
"bodyAccess": "Příslušenství na tělo",
|
||||
"mainHand": "Hlavní ruka",
|
||||
"offHand": "Druhá ruka",
|
||||
"pointsAvailable": "Dostupné body",
|
||||
"statPoints": "Stat Points",
|
||||
"pts": "Body"
|
||||
}
|
||||
@@ -170,6 +170,9 @@
|
||||
"questEggSquirrelText": "Squirrel",
|
||||
"questEggSquirrelMountText": "Squirrel",
|
||||
"questEggSquirrelAdjective": "bushy-tailed",
|
||||
"questEggSeaSerpentText": "Sea Serpent",
|
||||
"questEggSeaSerpentMountText": "Sea Serpent",
|
||||
"questEggSeaSerpentAdjective": "shimmering",
|
||||
"eggNotes": "Najdi líhnoucí lektvar, nalij ho na vejce a to se vylíhne v <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
|
||||
"hatchingPotionBase": "Základní",
|
||||
"hatchingPotionWhite": "Bílý",
|
||||
|
||||
@@ -656,6 +656,8 @@
|
||||
"armorMystery201802Notes": "This shiny armor reflects your strength of heart and infuses it into any Habiticans nearby who may need encouragement! Confers no benefit. February 2018 Subscriber Item.",
|
||||
"armorMystery201806Text": "Alluring Anglerfish Tail",
|
||||
"armorMystery201806Notes": "This sinuous tail features glowing spots to light your way through the deep. Confers no benefit. June 2018 Subscriber Item.",
|
||||
"armorMystery201807Text": "Sea Serpent Tail",
|
||||
"armorMystery201807Notes": "This powerful tail will propel you through the sea at incredible speeds! Confers no benefit. July 2018 Subscriber Item.",
|
||||
"armorMystery301404Text": "Steampunk oblek",
|
||||
"armorMystery301404Notes": "Elegantní a fešácký, joj! Nepřináší žádný benefit. Předmět pro předplatitele únor 3015.",
|
||||
"armorMystery301703Text": "Steampunk Peacock Gown",
|
||||
@@ -1062,6 +1064,8 @@
|
||||
"headMystery201805Notes": "This helm will make you the proudest and prettiest (possibly also the loudest) bird in town. Confers no benefit. May 2018 Subscriber Item.",
|
||||
"headMystery201806Text": "Alluring Anglerfish Helm",
|
||||
"headMystery201806Notes": "The mesmerizing light atop this helm will call all the creatures of the sea to your side. We urge you to use your glowy powers of attraction for good! Confers no benefit. June 2018 Subscriber Item.",
|
||||
"headMystery201807Text": "Sea Serpent Helm",
|
||||
"headMystery201807Notes": "The strong scales on this helm will protect you from any manner of oceanic foe. Confers no benefit. July 2018 Subscriber Item.",
|
||||
"headMystery301404Text": "Fešný cylindr",
|
||||
"headMystery301404Notes": "Fešný cylindr pro ty největší džentlmeny. Předmět pro předplatitele leden 2015. Nepřináší žádný benefit.",
|
||||
"headMystery301405Text": "Obyčejný cylindr",
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
"PMDisabledCaptionText": "You can still send messages, but no one can send them to you.",
|
||||
"block": "Blokovat",
|
||||
"unblock": "Odblokovat",
|
||||
"blockWarning": "Block - This will have no effect if the player is a moderator now or becomes a moderator in future.",
|
||||
"pm-reply": "Poslat odpověď",
|
||||
"inbox": "Příchozí zprávy",
|
||||
"messageRequired": "Je požadována zpráva.",
|
||||
@@ -255,7 +256,7 @@
|
||||
"confirmApproval": "Are you sure you want to approve this task?",
|
||||
"confirmNeedsWork": "Are you sure you want to mark this task as needing work?",
|
||||
"userRequestsApproval": "<%= userName %> requests approval",
|
||||
"userCountRequestsApproval": "<%= userCount %> request approval",
|
||||
"userCountRequestsApproval": "<%= userCount %> members request approval",
|
||||
"youAreRequestingApproval": "You are requesting approval",
|
||||
"chatPrivilegesRevoked": "You cannot do that because your chat privileges have been revoked.",
|
||||
"cannotCreatePublicGuildWhenMuted": "You cannot create a public guild because your chat privileges have been revoked.",
|
||||
@@ -380,6 +381,7 @@
|
||||
"bronzeTier": "Bronze Tier",
|
||||
"privacySettings": "Privacy Settings",
|
||||
"onlyLeaderCreatesChallenges": "Only the Leader can create Challenges",
|
||||
"onlyLeaderCreatesChallengesDetail": "With this option selected, ordinary group members cannot create Challenges for the group.",
|
||||
"privateGuild": "Private Guild",
|
||||
"charactersRemaining": "<%= characters %> characters remaining",
|
||||
"guildSummary": "Summary",
|
||||
@@ -467,5 +469,9 @@
|
||||
"howToRequireApprovalDesc2": "Group Leaders and Managers can approve completed Tasks directly from the Task Board or from the Notifications panel.",
|
||||
"whatIsGroupManager": "What is a Group Manager?",
|
||||
"whatIsGroupManagerDesc": "A Group Manager is a user role that do not have access to the group's billing details, but can create, assign, and approve shared Tasks for the Group's members. Promote Group Managers from the Group’s member list.",
|
||||
"goToTaskBoard": "Go to Task Board"
|
||||
"goToTaskBoard": "Go to Task Board",
|
||||
"sharedCompletion": "Shared Completion",
|
||||
"recurringCompletion": "None - Group task does not complete",
|
||||
"singleCompletion": "Single - Completes when any assigned user finishes",
|
||||
"allAssignedCompletion": "All - Completes when all assigned users finish"
|
||||
}
|
||||