Compare commits

...

68 Commits

Author SHA1 Message Date
Sabe Jones 42d823ab44 4.54.0 2018-07-26 18:20:56 +00:00
Sabe Jones f19331cfcc chore(i18n): update locales 2018-07-26 18:20:18 +00:00
Sabe Jones b7de7335ed Merge branch 'develop' into release 2018-07-26 18:15:33 +00:00
Sabe Jones 2e00ec5534 chore(news): Bailey 2018-07-26 13:12:15 -05:00
Sabe Jones a3af39ed25 4.53.0 2018-07-24 20:05:16 +00:00
Sabe Jones b57518732e chore(i18n): update locales 2018-07-24 20:04:40 +00:00
Sabe Jones a6a6aac400 chore(sprites): compile 2018-07-24 15:00:09 -05:00
Sabe Jones 48a92e77be feat(content): July Subscriber Items and Summer Splash Orcas 2018-07-24 14:59:49 -05:00
Matteo Pagliazzi d2a39a5124 fix #10511 (#10551) 2018-07-23 19:47:32 +02:00
Keith Holliday 7bead74b49 Removed extra options and fixed copy userID (#10538) 2018-07-21 08:11:29 -05:00
Keith Holliday 91e91788ce Change streak achievement snack to text type (#10534) 2018-07-21 08:11:09 -05:00
Keith Holliday 62c60ce520 Added copy to detail blocks wont work on mods (#10539)
* Added copy to detail blocks wont work on mods

* fix(wording): "moderator" clearer than "mod"
2018-07-21 08:10:54 -05:00
Keith Holliday 423eafbd4d Reset gift message (#10540) 2018-07-21 08:10:34 -05:00
Keith Holliday 004ab51c46 Allow for stat edits on mobile (#10535) 2018-07-21 08:09:57 -05:00
Keith Holliday f464403623 Display exact date on hover (#10536) 2018-07-21 08:09:36 -05:00
Keith Holliday 0fc66bef4e Fixed guild badge color text (#10537) 2018-07-21 08:09:16 -05:00
Keith Holliday 71636cd25e fixed new message count alignment (#10541) 2018-07-21 08:08:56 -05:00
Keith Holliday d5efb50d9b Update stat point title (#10543) 2018-07-21 08:08:36 -05:00
Keith Holliday 510e01effd Added user field limit to some routes (#10546)
* Added user field limit to some routes

* Added taskOrder and perferences

* Added contributor field
2018-07-20 23:24:33 -05:00
Texas Toland 0e648d85a0 Fix #10477 (#10530) 2018-07-20 15:55:18 -05:00
negue 7b562c45cf Fix Modal Stack - reopening modals (#10493)
* use fallback target.id - only scroll modal content (not the page)

* fix lint

* debug information

* add snackbar onClick callback - use notification instead of modal for joined-challenge

* revert console.log / fix lint
2018-07-20 15:50:40 -05:00
negue b7a46637d5 check genericPurchase rejections and show error (#10526) 2018-07-20 15:33:23 -05:00
Sabe Jones 284b2cc413 Group Tasks Shared Completion (#10515)
* WIP(groups): add shared completion prop
Also fix an issue where the Needs Approval toggle would not read/save 
correctly.

* fix(groups): save group options on task create
Also, correct count of assigned members when viewing user is among 
assignments

* fix(groups): display correct messages in two places

* fix(tasks): eliminate console error related to filtering
Also localize a group plans string

* WIP(groups): implement single completion for approval workflow

* WIP(groups): Add shared completion handling to no-approval-needed flow

* WIP(groups): cover approval flow case for all-assigned
Also save new field on initial task creation

* fix(tasks): use default sharedCompletion value when creating tasks

* WIP(tests): non-working draft test

* Added completed todo to group query

* WIP(group-tasks): fix bugs, add tests

* refactor(group-tasks): deleteMany op, add more tests

* refactor(group-tasks): move shared completion handling to lib

* WIP(group-tasks): broken refactor

* WIP(group-tasks): await all the things

* Turned complete master task to save

* WIP(group-tasks): show completed

* fix(filtering): don't try to filter if no list is passed

* refactor(group-tasks): load completed to-dos on demand, not at start

* fix(group-tasks): don't double up on repeat visits

* fix(group-tasks): include brief explanation in dropdown

* fix(group-tasks): improve wording some more
2018-07-20 12:29:44 -05:00
negue 8b69540e71 FIX: Challenge Creation without Group not found error (#10525)
* prevent loading the party, if the user isn't part of one

* check for the party id too
2018-07-19 22:48:17 -05:00
Keith Holliday 8d9a4e97a8 Fixed display points for the stat save button (#10522) 2018-07-19 22:03:43 -05:00
Sabe Jones f31a82c8f2 4.52.2 2018-07-19 19:04:53 +00:00
Sabe Jones 8bc02e82ee chore(i18n): update locales 2018-07-19 19:04:38 +00:00
Sabe Jones 9040f9f04e 4.52.1 2018-07-19 14:01:16 -05:00
Sabe Jones ff82c37d5f chore(news): Bailey 2018-07-19 14:00:53 -05:00
Sabe Jones 37364b0700 Merge branch 'develop' into release 2018-07-19 13:38:00 -05:00
Sabe Jones 11cfb3920a 4.52.0 2018-07-17 19:10:41 +00:00
Sabe Jones f5468d3771 chore(i18n): update locales 2018-07-17 19:09:37 +00:00
Sabe Jones 99882d09ab chore(sprites): compile 2018-07-17 14:05:24 -05:00
Sabe Jones 7034d135d5 feat(content): Sea Serpent Pet Quest 2018-07-17 14:05:09 -05:00
Sabe Jones 034c0c9bb5 4.51.4 2018-07-16 21:12:50 +00:00
Sabe Jones e0711655f0 chore(i18n): update locales 2018-07-16 21:12:33 +00:00
Sabe Jones 71e162eed5 chore(news): Bailey 2018-07-16 16:10:04 -05:00
Matteo Pagliazzi 8eac8732c5 fix(tasks): do not load completed todos if not necessary 2018-07-16 12:02:37 +02:00
Keith Holliday 896a1b74b6 Added new award flow to challenges (#10512) 2018-07-13 21:58:28 -05:00
Keith Holliday 3b36046a6a Removed member count code (#10518) 2018-07-13 06:06:33 -05:00
negue 07991817e7 check balanceRemoved for analytics call (#10506) 2018-07-13 10:51:49 +02:00
negue 47b75156fa reset flag comment (#10507) 2018-07-13 10:50:17 +02:00
Sabe Jones c630486fef fix(errors): handle non-array-style errors again 2018-07-12 16:47:59 -05:00
negue 0a070316b5 fix xml export (#10505)
* add /export webpack-proxy - fix xml export

* fix lint / add xmlMode
2018-07-12 15:49:22 -05:00
negue f6b34e85df Max 3000 Character Limit on Chat-Messages (#10494)
* limit chat length to 3000

* add test
2018-07-12 15:40:04 -05:00
Isabelle Lavandero 2946f0df15 Update signup error messages (#10483)
* prints first error message only

* update signup error messages, missing password not working (wip)

* remove alerts, show notEmpty, first error only per param, update unit test

* move changes to client side
2018-07-12 15:27:02 -05:00
Vinicius 614d9a920a Change track-habits.png to the correct pictures available on Zeplin. (#10514) 2018-07-12 15:13:56 -05:00
aszlig 454524fb5b member-details: Only add 1px margin when condensed (#10504)
As reported in #10502, adding a 1 pixel right margin to *all* nodes with
the member-stats class will affect the display when you click on the
avatar as well, which will then break the layout because of that
additional margin.

Instead of adding the margin to .member-stats in general, let's just add
it to the condensed version so that it really just addresses the
flickering on Chrome/Chromium as reported in #10379.

I've tested whether the flickering still happens via a small
xdotool-loop (just to make sure I don't get too shaky with the mouse):

for i in $(seq 500 508); do xdotool mousemove "$i" 250; sleep 5; done

The flickering doesn't happen anymore and the layout in the party
members overview and the stats overview when you click on the avatar is
no longer distorted.

Signed-off-by: aszlig <aszlig@nix.build>
Fixes: #10502
2018-07-12 15:12:40 -05:00
Vinicius abc0777412 Add word-break: break-word to .sortable-tasks class to prevent links and words to get out of the task box (#10495) 2018-07-12 15:10:23 -05:00
James Robinson 6972eb8f8f Prevent login error text overflow (#10450) 2018-07-12 15:08:13 -05:00
Brian Fenton 535ee2b2a7 Adding hand cursor to FAQ headings, and ability to address answers via URL hash (#10260)
* Turning H2s into anchors to add hand cursor and to create addressable page fragments
adding ref to target individual entries
adding scroll handler to make sure expanded result is in view.

* combining click handler directives as per CR

* changing question display to always render, then hide via CSS, vs only render when state changes.

* updating pug template to include heading in each URL fragment

* simplifying logic & moving to vue-bootstrap accordion since multiple open panels is not required

* adding pointer cursor to FAQ headings

* moving initial hash checking to data prop instead of mounted so it does not trigger oddities on on hash change (re-mount)

* using new pug HTML for bootstrap collapse

* removing extraneous markup

* updating styling to match existing page

* removing fancier than necessary markup, and attendant styles

* using more standard event property
2018-07-12 15:07:49 -05:00
Jim Pollaro c9755bee7c Logout Changes #9915 (#10022)
* Added session check before route changes, but express isn't finding route

* Added a logout component. Changed route to logout on server. Typing 'logout' in URL will logout of Vue + Express

* Removed commented text from previous version

* Updated logout function to comply with formatting and eliminate unused blocks

* Added package-lock.json back

* package-lock.json

* recreated package-lock file

* fix(auth): allow logout from direct visit to /logout path

* fix(merge): clean up more misc changes

* fix(merge): remove extra file
2018-07-12 15:07:08 -05:00
Sabe Jones f810fff6fc 4.51.3 2018-07-12 19:10:02 +00:00
Sabe Jones 5bbe59c52d chore(i18n): update locales 2018-07-12 19:09:42 +00:00
Sabe Jones 3f52401384 chore(news): Bailey 2018-07-12 14:05:40 -05:00
Matteo Pagliazzi e7944b3d98 iOS push notifications, use node-apn (#10517)
* fixing typos in comments. yes, I am that kind of nerd

* replacing push-notify with node-apn in deps and in pushNotifications.js

* updating calling code and tests to use node-apn

* updating APN configs to new format

* migrating team ID and key ID to config.json

* update code to use env variables and add correct topic
2018-07-12 12:56:15 +02:00
Sabe Jones 08e925e3da Merge branch 'release' into develop 2018-07-10 17:26:37 +00:00
Sabe Jones 16c9e42ad8 4.51.2 2018-07-10 17:24:18 +00:00
Sabe Jones 0e1d00c95f chore(i18n): update locales 2018-07-10 17:23:40 +00:00
Sabe Jones 166f4683ca feat(content): enable Splashy Skins 2018-07-10 12:18:41 -05:00
Keith Holliday 1fcc0d8d3a Ensured redirect is using base (#10497) 2018-07-09 10:08:19 -05:00
Matteo Pagliazzi 8a4c4e10f1 remove sensitive info from logs 2018-07-08 10:43:28 +02:00
Maurício El Uri 18ed0fe446 Fixed the unnecessary whitespace to the right of landing page (#10435) (#10490) 2018-07-06 15:57:36 -05:00
Vinicius 1f9ebeb629 Set .resting-banner z-index to 1300, set #progress .bar z-index to 1600 (#10492) 2018-07-06 15:55:44 -05:00
Sabe Jones b9d83122d1 fix(tasks): eliminate console error related to filtering
Also localize a group plans string
2018-07-06 15:43:27 -05:00
Sabe Jones d1f7e64156 fix(groups): display correct messages in two places 2018-07-06 13:21:28 -05:00
Sabe Jones 0f8e7416f8 fix(groups): save group options on task create
Also, correct count of assigned members when viewing user is among 
assignments
2018-07-06 10:30:09 -05:00
Sabe Jones 1c8b0f92df Small Group Plan fixes (#10499)
* fix(groups): better margins, don't close whole modal on close assigned members list

* fix(groups): proper sticky toggle switch
2018-07-05 16:37:06 -05:00
461 changed files with 13472 additions and 11716 deletions
+3
View File
@@ -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": {
+2 -2
View File
@@ -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 });
+99
View File
@@ -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;
+114 -41
View File
@@ -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",
+2 -2
View File
@@ -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",
+7 -6
View File
@@ -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);
});
});
+5 -1
View File
@@ -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,
},
+39 -6
View File
@@ -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;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

+11 -2
View File
@@ -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;
}
+2 -1
View File
@@ -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', {
+13
View File
@@ -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({
+3 -2
View File
@@ -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: ''});
+8 -1
View File
@@ -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') }}
+1 -1
View File
@@ -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;
+4 -2
View File
@@ -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);
+35 -15
View File
@@ -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}`);
},
},
};
+3 -7
View File
@@ -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');
}
+18 -3
View File
@@ -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
+50 -32
View File
@@ -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}}&nbsp;
.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) {
+22 -8
View File
@@ -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');
+9 -4
View File
@@ -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,
});
},
},
+2
View File
@@ -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 },
{
+1 -1
View File
@@ -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';
}
+5
View File
@@ -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": "Изпълнявайки целите си в истинския живот, Вие качихте ниво и здравето Ви беше запълнено!",
+1
View File
@@ -131,6 +131,7 @@
"locationRequired": "Мястото на предизвикателството е задължително („Добавяне в“)",
"categoiresRequired": "Задължително е да бъде избрана поне една категория",
"viewProgressOf": "Преглед на напредъка на",
"viewProgress": "Преглед на напредъка",
"selectMember": "Изберете член",
"confirmKeepChallengeTasks": "Искате ли да задържите задачите от предизвикателството?",
"selectParticipant": "Изберете участник"
+1 -1
View File
@@ -219,6 +219,6 @@
"bodyAccess": "Аксесоар за тяло",
"mainHand": "Основна ръка",
"offHand": "Страничен",
"pointsAvailable": "Налични точки",
"statPoints": "Stat Points",
"pts": "точки"
}
+3
View File
@@ -170,6 +170,9 @@
"questEggSquirrelText": "Катерица",
"questEggSquirrelMountText": "Катерица",
"questEggSquirrelAdjective": "рунтава",
"questEggSeaSerpentText": "Морски змей",
"questEggSeaSerpentMountText": "Морски змей",
"questEggSeaSerpentAdjective": "блещукащ",
"eggNotes": "Намерете излюпваща отвара, която да излеете върху това яйце и от него ще се излюпи <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Нормален цвят",
"hatchingPotionWhite": "Бял цвят",
+40 -36
View File
@@ -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": "Няма аксесоар за гръб",
+12 -6
View File
@@ -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"
}
+5 -5
View File
@@ -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 ноември",
+1 -1
View File
@@ -98,7 +98,7 @@
"guildQuestsNotSupported": "Гилдиите не могат да бъдат канени в мисии.",
"questNotOwned": "Не притежавате този свитък с мисия.",
"questNotGoldPurchasable": "Мисията „<%= key %>“ не може да бъде купена със злато.",
"questNotGemPurchasable": "Quest \"<%= key %>\" is not a Gem-purchasable quest.",
"questNotGemPurchasable": "Мисията „<%= key %>“ не може да бъде купена с диаманти.",
"questLevelTooHigh": "Трябва да бъдете ниво <%= level %>, за да започнете тази мисия.",
"questAlreadyUnderway": "Групата Ви е вече изпълнява мисия. Опитайте отново, когато текущата мисия приключи.",
"questAlreadyAccepted": "Вече сте приели поканата за мисията.",
+7 -1
View File
@@ -604,5 +604,11 @@
"cuddleBuddiesText": "Пакет мисии „Пухкави приятелчета“",
"cuddleBuddiesNotes": "Съдържа: „Зайчето-убиец“, „Нечестивият пор“ и „Бандата на морските свинчета“. Наличен до 31 май.",
"aquaticAmigosText": "Пакет мисии „Водни дружки“",
"aquaticAmigosNotes": "Съдържа: „Вълшебният саламандър“, „Кракенът на незавършеността“ и „Зовът на Октотулу“. Наличен до 30 юни."
"aquaticAmigosNotes": "Съдържа: „Вълшебният саламандър“, „Кракенът на незавършеността“ и „Зовът на Октотулу“. Наличен до 30 юни.",
"questSeaSerpentText": "Опасност в дълбините: Нападението на морския змей!",
"questSeaSerpentNotes": "С всичките си серии се чувстваш като късметлия – и сега е идеално време за посещение на хиподрума за морски кончета. Качваш се на подводницата от пристанище Усърдие и се настаняваш удобно за пътуването до Мудноград, но едва се потапяте, когато нещо удря подводницата и запраща всички пътници на пода. „Какво става“ — крещи @AriesFaries?<br><br>Поглеждаш през близкия люк и ужас виждаш как покрай него преминава стена от блестящи люспи. „Морски змей“ — обявява капитан @Witticaster по вътрешното радио! — „Пригответе се, връща се!“ Сграбчвайки подлакътниците на седалката си, виждаш незавършените си задачи да преминават пред погледа ти. „Може би, ако работим заедно и ги завършим“, мислиш си, „ще прогоним чудовището“.",
"questSeaSerpentCompletion": "Сломен от усърдието ви, морският змей отстъпва и изчезва в дълбините. Когато пристигаш в Мудноград, най-после си отдъхваш, и забелязваш, че @*~Seraphina~ се приближава с три яйца. „Ето, вземи ги“ — казва тя. — „Явно знаеш как да се оправяш с морските змейове!“ Когато вземаш яйцата, отново се заричаш да бъдеш по-сериозен със задачите си, за да не се повтори тази случка.",
"questSeaSerpentBoss": "Могъщият морски змей",
"questSeaSerpentDropSeaSerpentEgg": "Морски змей (яйце)",
"questSeaSerpentUnlockText": "Отключва възможността за купуване на яйца на морски змей от пазара."
}
+1
View File
@@ -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> Ако ежедневните Ви задачи се подновяват в грешно време, повторете тази проверка на всичките си останали компютри и чрез браузъра на всички свои мобилни устройства.",
+1 -1
View File
@@ -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!",
+1
View File
@@ -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"
+1 -1
View File
@@ -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"
}
+3
View File
@@ -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ý",
+4
View File
@@ -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",
+8 -2
View File
@@ -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 Groups 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"
}

Some files were not shown because too many files have changed in this diff Show More