Compare commits

...

88 Commits

Author SHA1 Message Date
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
Sabe Jones
75e5b20f93 4.51.1 2018-07-05 15:14:05 +00:00
Sabe Jones
f9db432794 chore(i18n): update locales 2018-07-05 15:12:59 +00:00
Sabe Jones
6cec7cbba2 Merge branch 'release' into develop 2018-07-03 17:39:27 +00:00
Sabe Jones
f76d097313 4.51.0 2018-07-03 17:38:44 +00:00
Sabe Jones
59af471438 chore(i18n): update locales 2018-07-03 17:38:00 +00:00
Sabe Jones
d0da303b7d chore(sprites): fix shop icon canvases, compile 2018-07-03 12:33:20 -05:00
Sabe Jones
596383e7a8 feat(content): Armoire and Backgrounds July 2018 2018-07-03 12:26:20 -05:00
Matteo Pagliazzi
accba7fc13 fix bug in task approval notification 2018-07-03 11:23:44 +02:00
Sabe Jones
dfc54f1600 Merge branch 'release' into develop 2018-07-02 19:58:23 +00:00
Sabe Jones
b76ab58e3e 4.50.6 2018-07-02 19:58:00 +00:00
Sabe Jones
6f093a94c4 chore(i18n): update locales 2018-07-02 19:57:39 +00:00
Sabe Jones
a9a9e7a4ab chore(sprites): compile 2018-07-02 14:51:59 -05:00
Sabe Jones
b2058ec23d chore(news): Bailey challenge announcements 2018-07-02 14:51:40 -05:00
Keith Holliday
2461f53cf5 4.50.5 2018-07-01 18:25:23 -05:00
Keith Holliday
73fc288f3b 4.50.4 2018-07-01 18:25:20 -05:00
Keith Holliday
ea78b6feb9 Changed docker file names (#10489) 2018-07-01 18:23:40 -05:00
Keith Holliday
7c141614ed Api login party invites (#10486)
* Fixed incorrect variable

* Fixed redirect params
2018-06-30 22:06:18 -05:00
Alys
c072935e80 document the dueDate parameter for API task functions - partial fix for #8087 2018-06-30 15:55:27 +10:00
Alys
54e49ca3b9 adjust README file for recent changes in wiki Blacksmith pages
Also replace non-ascii quotes with ascii ones (nicer for
command-line reading).
2018-06-30 15:05:05 +10:00
Sabe Jones
b16a245d61 Merge branch 'release' into develop 2018-06-29 19:06:20 +00:00
Sabe Jones
0a8109e496 4.50.3 2018-06-29 19:05:53 +00:00
Sabe Jones
cdfcc6419f chore(i18n): update locales 2018-06-29 19:03:36 +00:00
Matteo Pagliazzi
9c25c2452f fixes #10485 2018-06-29 20:29:14 +02:00
Matteo Pagliazzi
573f2e4732 fix preening when history entries are null 2018-06-29 20:03:45 +02:00
Keith Holliday
81ffcf9c1b Added login path for Spritely (#10484) 2018-06-29 11:35:22 -05:00
Sabe Jones
d8925a8811 Merge branch 'release' into develop 2018-06-29 00:22:18 +00:00
Sabe Jones
217c16988b 4.50.2 2018-06-29 00:21:56 +00:00
Sabe Jones
de7f953b67 chore(i18n): update locales 2018-06-29 00:20:37 +00:00
SabreCat
8ee2a02e73 chore(news): Bailey 2018-06-29 00:18:13 +00:00
negue
d9b573b430 AbstractGemItemOperation - BuyQuestWithGemOperation (#10476) 2018-06-28 12:37:21 +02:00
Keith Holliday
cbcf5a03e1 Ensured leader doesn't change with group plans (#10480) 2018-06-28 11:57:24 +02:00
Matteo Pagliazzi
487523f64b client: disable broken test 2018-06-28 11:04:35 +02:00
Matteo Pagliazzi
74cfc2cf52 add ability to specify pool size for mongodb (#10481) 2018-06-28 11:02:26 +02:00
Sabe Jones
2d489e870f Merge branch 'release' into develop 2018-06-27 18:34:06 +00:00
Sabe Jones
6eb484605a fix(messages): clarify opt-in/out wording and leave label static (#10470) 2018-06-27 19:13:13 +02:00
Sabe Jones
8969b755a6 fix(analytics): remove spurious click tracking (#10469) 2018-06-27 19:12:51 +02:00
Sabe Jones
0062e5b1f1 fix(time-travelers): don't timeshift background without Hourglass (#10468) 2018-06-27 19:12:36 +02:00
Sabe Jones
50b98d8d92 fix(deletion): show feedback for social accounts (#10467) 2018-06-27 19:12:18 +02:00
Isabelle Lavandero
7ddf4b1f7b Remove "add multiple" tip once a task has been added (fixes #10440) (#10459)
* remove tip once a task has been added

* blur quickadd on enter but not on shift

* blur quickadd after tasks are created
2018-06-27 19:08:45 +02:00
Dexx Mandele
c91da86b89 Remember equipment drawer tab (#10458)
* Remember equipment drawer tab

* Split local setting value constants
2018-06-27 19:08:21 +02:00
Jerell Mendoza
d549fea4ed 10282: Added code for blocking party and guild invitations from block… (#10454)
* 10282: Added code for blocking party and guild invitations from blocked players, added tests

* 10282: fixed test label

* Update POST-groups_invite.test.js

removed `it.only` which was used for testing
2018-06-27 19:07:57 +02:00
Patricia Beier
a362914f93 added ctrl+enter to private messages #10413 (#10436)
* added ctrl+enter to private messages #10413

* Fixes #10413
2018-06-27 19:07:41 +02:00
Hayden Betts
61001d0e9a WIP Fix flicker when user mouses over the very leftmost edge of party member avatar (#10407)
* added 1px margin-right to .member-stats

* added unit test for flicker prevention style

* remove .only() from unit test

* rewrote margin test using computed style
2018-06-27 19:07:21 +02:00
Sabe Jones
bd5c4a08e2 Merge branch 'release' into develop 2018-06-26 20:45:37 +00:00
Matteo Pagliazzi
50ebdd1ece tasks hsitory migration: prevent it from running twice 2018-06-25 23:14:35 +02:00
491 changed files with 33821 additions and 32153 deletions

View File

@@ -1,18 +1,29 @@
FROM node:8
# Install global packages
RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN cp config.json.example config.json
RUN npm install
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
FROM node:8
ENV ADMIN_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
# Install global packages
RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch release https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN gulp build:prod --force
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["node", "./website/transpiled-babel/index.js"]

18
Dockerfile-Dev Normal file
View File

@@ -0,0 +1,18 @@
FROM node:8
# Install global packages
RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN cp config.json.example config.json
RUN npm install
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]

View File

@@ -1,29 +0,0 @@
FROM node:8
ENV ADMIN_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
ENV AMAZON_PAYMENTS_SELLER_ID AMQ3SB4SG5E91
ENV AMPLITUDE_KEY e8d4c24b3d6ef3ee73eeba715023dd43
ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA
# Install global packages
RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch v4.49.1 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN gulp build:prod --force
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["node", "./website/transpiled-babel/index.js"]

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": {

View File

@@ -1,4 +1,4 @@
// const migrationName = 'habits-one-history-entry-per-day';
const migrationName = 'habits-one-history-entry-per-day';
const authorName = 'paglias'; // in case script author needs to know when their ...
const authorUuid = 'ed4c688c-6652-4a92-9d03-a5a79844174a'; // ... own data is done
@@ -14,7 +14,9 @@ const dbTasks = monk(connectionString).get('tasks', { castIds: false });
const dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) {
let query = {};
let query = {
migration: {$ne: migrationName},
};
if (lastId) {
query._id = {
@@ -127,6 +129,11 @@ function updateUser (user) {
.then(habits => {
return Promise.all(habits.map(habit => updateHabit(habit, timezoneOffset, dayStart)));
})
.then(() => {
return dbUsers.update({_id: user._id}, {
$set: {migration: migrationName},
});
})
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);

View File

@@ -1,4 +1,4 @@
let migrationName = '20180102_takeThis.js'; // Update per month
let migrationName = '20180702_takeThis.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
@@ -6,15 +6,16 @@ let authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
* Award Take This ladder items to participants in this month's challenge
*/
let monk = require('monk');
let connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
let dbUsers = monk(connectionString).get('users', { castIds: false });
import monk from 'monk';
import nconf from 'nconf';
const CONNECTION_STRING = nconf.get('MIGRATION_CONNECT_STRING'); // FOR TEST DATABASE
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},
challenges: {$in: ['5f70ce5b-2d82-4114-8e44-ca65615aae62']}, // Update per month
challenges: {$in: ['f0481f95-1dde-4ae7-a876-d19502a45d61']}, // Update per month
};
if (lastId) {

155
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "4.50.1",
"version": "4.52.2",
"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",

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.50.1",
"version": "4.52.2",
"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",

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;
});
});

View File

@@ -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';

View File

@@ -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});

View File

@@ -114,6 +114,19 @@ describe('Post /groups/:groupId/invite', () => {
});
});
it('returns error when recipient has blocked the senders', async () => {
const inviterNoBlocks = await inviter.update({'inbox.blocks': []});
let userWithBlockedInviter = await generateUser({'inbox.blocks': [inviter._id]});
await expect(inviterNoBlocks.post(`/groups/${group._id}/invite`, {
uuids: [userWithBlockedInviter._id],
}))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('notAuthorizedToSendMessageToThisUser'),
});
});
it('invites a user to a group by uuid', async () => {
let userToInvite = await generateUser();

View File

@@ -0,0 +1,23 @@
import Vue from 'vue';
import MemberDetailsComponent from 'client/components/memberDetails.vue';
describe('Members Details Component', () => {
let CTor;
let vm;
beforeEach(() => {
CTor = Vue.extend(MemberDetailsComponent);
vm = new CTor().$mount();
});
afterEach(() => {
vm.$destroy();
});
xit('prevents flickering by setting a 1px margin-right on elements of class member-stats', () => {
const memberstats = vm.$el.querySelector('.member-stats');
const style = window.getComputedStyle(memberstats, null);
const marginRightProp = style.getPropertyValue('margin-right');
expect(marginRightProp).to.equal('1');
});
});

View File

@@ -0,0 +1,94 @@
import pinnedGearUtils from '../../../../website/common/script/ops/pinnedGearUtils';
import {
NotAuthorized,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import {
generateUser,
} from '../../../helpers/common.helper';
import {BuyQuestWithGemOperation} from '../../../../website/common/script/ops/buy/buyQuestGem';
describe('shared.ops.buyQuestGems', () => {
let user;
let goldPoints = 40;
let analytics = {track () {}};
function buyQuest (_user, _req, _analytics) {
const buyOp = new BuyQuestWithGemOperation(_user, _req, _analytics);
return buyOp.purchase();
}
before(() => {
user = generateUser({'stats.class': 'rogue'});
});
beforeEach(() => {
sinon.stub(analytics, 'track');
sinon.spy(pinnedGearUtils, 'removeItemByPath');
});
afterEach(() => {
analytics.track.restore();
pinnedGearUtils.removeItemByPath.restore();
});
context('successful purchase', () => {
let userGemAmount = 10;
before(() => {
user.balance = userGemAmount;
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = 0;
user.purchased.plan.customerId = 'customer-id';
user.pinnedItems.push({type: 'quests', key: 'gryphon'});
});
it('purchases quests', () => {
let key = 'gryphon';
buyQuest(user, {params: {key}});
expect(user.items.quests[key]).to.equal(1);
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
});
});
context('bulk purchase', () => {
let userGemAmount = 10;
beforeEach(() => {
user.balance = userGemAmount;
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = 0;
user.purchased.plan.customerId = 'customer-id';
});
it('errors when user does not have enough gems', (done) => {
user.balance = 1;
let key = 'gryphon';
try {
buyQuest(user, {
params: {key},
quantity: 2,
});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('notEnoughGems'));
done();
}
});
it('makes bulk purchases of quests', () => {
let key = 'gryphon';
buyQuest(user, {
params: {key},
quantity: 3,
});
expect(user.items.quests[key]).to.equal(4);
});
});
});

View File

@@ -121,7 +121,6 @@ describe('shared.ops.purchase', () => {
user.pinnedItems.push({type: 'eggs', key: 'Wolf'});
user.pinnedItems.push({type: 'hatchingPotions', key: 'Base'});
user.pinnedItems.push({type: 'food', key: SEASONAL_FOOD});
user.pinnedItems.push({type: 'quests', key: 'gryphon'});
user.pinnedItems.push({type: 'gear', key: 'headAccessory_special_tigerEars'});
user.pinnedItems.push({type: 'bundles', key: 'featheredFriends'});
});
@@ -157,16 +156,6 @@ describe('shared.ops.purchase', () => {
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
});
it('purchases quests', () => {
let type = 'quests';
let key = 'gryphon';
purchase(user, {params: {type, key}});
expect(user.items[type][key]).to.equal(1);
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
});
it('purchases gear', () => {
let type = 'gear';
let key = 'headAccessory_special_tigerEars';

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,
},

View File

@@ -1,21 +1,21 @@
# Running
For information about installing Habitica locally, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally) and for information about running the local client, refer to the ["Run Habitica" section](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally#Run_Habitica) in that page.
For information about installing and running Habitica locally, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
# Preparation Reading
- Vue 2 (https://vuejs.org)
- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/
- Were using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. Theyre implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/
- We're using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. They're implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/
- SemanticUI is the UI framework http://semantic-ui.com/. So far Ive only used the CSS part, it also has JS plugins but Ive yet to use them. It supports theming so if its not too difficult well want to customize the base theme with our own styles instead of writing CSS rules to override the original styling.
- SemanticUI is the UI framework http://semantic-ui.com/. So far I've only used the CSS part, it also has JS plugins but I've yet to use them. It supports theming so if it's not too difficult we'll want to customize the base theme with our own styles instead of writing CSS rules to override the original styling.
The code is in `/website/client`. Were using something very similar to Vuex (equivalent of Reacts Redux) for state management http://vuex.vuejs.org/en/index.html
The code is in `/website/client`. We're using something very similar to Vuex (equivalent of React's Redux) for state management http://vuex.vuejs.org/en/index.html
The API is almost the same except that we dont use mutations but only actions because it would make it difficult to work with common code
The API is almost the same except that we don't use mutations but only actions because it would make it difficult to work with common code
The project is developed directly in the `develop` branch as long as well be able to avoid splitting it into a different branch.
The project is developed directly in the `develop` branch as long as we'll be able to avoid splitting it into a different branch.
So far most of the work has been on the template, so theres no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: its basically a Flux implementation: theres a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
So far most of the work has been on the template, so there's no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it's basically a Flux implementation: there's a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
For further resources, see [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).

View File

@@ -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,
});

View File

@@ -1,66 +1,78 @@
.promo_aquatic_glass_potions {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -627px 0px;
background-position: -853px 0px;
width: 141px;
height: 441px;
}
.promo_armoire_backgrounds_201806 {
.promo_armoire_backgrounds_201807 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -627px -442px;
background-position: -853px -442px;
width: 141px;
height: 441px;
}
.promo_bundle_aquaticAmigos {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -142px -533px;
width: 423px;
height: 147px;
}
.promo_ios {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 325px;
height: 336px;
background-position: -477px 0px;
width: 375px;
height: 361px;
}
.promo_mystery_201806 {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -769px -442px;
background-position: -995px -442px;
width: 121px;
height: 114px;
}
.promo_seafoam {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -769px 0px;
background-position: -995px 0px;
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 -681px;
background-position: -401px -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 -337px;
background-position: 0px -365px;
width: 141px;
height: 588px;
}
.promo_take_this {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -769px -557px;
background-position: -995px -557px;
width: 96px;
height: 69px;
}
.scene_families {
.promo_unconventional_armor {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -142px -337px;
width: 345px;
height: 195px;
background-position: -518px -365px;
width: 180px;
height: 180px;
}
.scene_moderators {
.scene_pomodoro {
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -326px 0px;
width: 300px;
height: 300px;
background-position: -142px -552px;
width: 258px;
height: 258px;
}

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

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: 137 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 KiB

After

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 KiB

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 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: 150 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: 158 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 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

View File

@@ -58,7 +58,6 @@
<script>
import Avatar from '../avatar';
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import percent from '../../../common/script/libs/percent';
import {maxHealth} from '../../../common/script/index';
@@ -82,14 +81,6 @@ export default {
return `${Math.ceil(this.user.stats.hp)} / ${this.maxHealth}`;
},
},
mounted () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Health Warning',
});
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'low-health');

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', {

View File

@@ -0,0 +1,13 @@
<script>
export default {
components: {},
methods: {
async logout () {
return await this.$store.dispatch('auth:logout');
},
},
created () {
this.logout();
},
};
</script>

View File

@@ -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', {

View File

@@ -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', {

View File

@@ -34,7 +34,7 @@
.challenge-description(v-markdown='challenge.summary')
.well-wrapper(v-if="fullLayout")
.well
div(v-for="task in tasksData", :class="{'muted': task.value === 0}", v-once)
div(v-for="task in tasksData", :class="{'muted': task.value === 0}")
.number
.svg-icon(v-html="task.icon", :class="task.label + '-icon'")
span.value {{ task.value }}

View File

@@ -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>

View File

@@ -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');
});
},

View File

@@ -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

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) {

View File

@@ -164,12 +164,6 @@ export default {
},
methods: {
shareUserId () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Health Warning',
});
this.shareUserIdShown = !this.shareUserIdShown;
},
async createParty () {

View File

@@ -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",
)
@@ -365,13 +365,6 @@ export default {
alert(this.$t('notEnoughGems'));
return;
// @TODO return $rootScope.openModal('buyGems', {track:"Gems > Gems > Create Group"});
// @TODO when modal is implemented, enable analytics
/* Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Health Warning',
}); */
}
let errors = [];

View File

@@ -54,7 +54,6 @@ import { mapState } from 'client/libs/store';
import filter from 'lodash/filter';
import map from 'lodash/map';
import notifications from 'client/mixins/notifications';
import * as Analytics from 'client/libs/analytics';
export default {
mixins: [notifications],
@@ -65,14 +64,6 @@ export default {
emails: [],
};
},
mounted () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Invite Friends',
});
},
computed: {
...mapState({user: 'user.data'}),
inviter () {

View File

@@ -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>

View File

@@ -36,7 +36,7 @@
drawer(
:title="$t('equipment')",
:errorMessage="(costume && !user.preferences.costume) ? $t('costumeDisabled') : null",
:errorMessage="(costumeMode && !user.preferences.costume) ? $t('costumeDisabled') : null",
:openStatus='openStatus',
v-on:toggled='drawerToggled'
)
@@ -44,18 +44,18 @@
.drawer-tab-container
.drawer-tab.text-right
a.drawer-tab-text(
@click="costume = false",
:class="{'drawer-tab-text-active': costume === false}",
@click="selectDrawerTab('equipment')",
:class="{'drawer-tab-text-active': !costumeMode}",
) {{ $t('equipment') }}
.clearfix
.drawer-tab.float-left
a.drawer-tab-text(
@click="costume = true",
:class="{'drawer-tab-text-active': costume === true}",
@click="selectDrawerTab('costume')",
:class="{'drawer-tab-text-active': costumeMode}",
) {{ $t('costume') }}
toggle-switch.float-right.align-with-tab(
:label="$t(costume ? 'useCostume' : 'autoEquipBattleGear')",
:label="$t(costumeMode ? 'useCostume' : 'autoEquipBattleGear')",
:checked="user.preferences[drawerPreference]",
@change="changeDrawerPreference",
:hoverText="$t(drawerPreference+'PopoverText')",
@@ -77,7 +77,7 @@
template(slot="itemBadge", slot-scope="context")
starBadge(
:selected="true",
:show="!costume || user.preferences.costume",
:show="!costumeMode || user.preferences.costume",
@click="equipItem(context.item)",
)
div(
@@ -109,7 +109,7 @@
template(slot="itemBadge", slot-scope="context")
starBadge(
:selected="activeItems[context.item.type] === context.item.key",
:show="!costume || user.preferences.costume",
:show="!costumeMode || user.preferences.costume",
@click="equipItem(context.item)",
)
template(slot="popoverContent", slot-scope="context")
@@ -119,7 +119,7 @@
:item="gearToEquip",
@equipItem="equipItem($event)",
@change="changeModalState($event)",
:costumeMode="costume",
:costumeMode="costumeMode",
:isEquipped="gearToEquip == null ? false : activeItems[gearToEquip.type] === gearToEquip.key"
)
</template>
@@ -185,7 +185,7 @@ export default {
itemsPerLine: 9,
searchText: null,
searchTextThrottled: null,
costume: false,
costumeMode: false,
groupBy: 'type', // or 'class'
gearTypesToStrings: Object.freeze({ // TODO use content.itemList?
weapon: i18n.t('weaponCapitalized'),
@@ -219,11 +219,24 @@ export default {
},
mounted () {
const drawerState = getLocalSetting(CONSTANTS.keyConstants.EQUIPMENT_DRAWER_STATE);
if (drawerState === CONSTANTS.valueConstants.DRAWER_CLOSED) {
if (drawerState === CONSTANTS.drawerStateValues.DRAWER_CLOSED) {
this.$store.state.equipmentDrawerOpen = false;
}
this.costumeMode = getLocalSetting(CONSTANTS.keyConstants.CURRENT_EQUIPMENT_DRAWER_TAB) === CONSTANTS.equipmentDrawerTabValues.COSTUME_TAB ? true : false;
},
methods: {
selectDrawerTab (tabName) {
let tabNameValue;
if (tabName === 'costume') {
tabNameValue = CONSTANTS.equipmentDrawerTabValues.COSTUME_TAB;
this.costumeMode = true;
} else {
tabNameValue = CONSTANTS.equipmentDrawerTabValues.EQUIPMENT_TAB;
this.costumeMode = false;
}
setLocalSetting(CONSTANTS.keyConstants.CURRENT_EQUIPMENT_DRAWER_TAB, tabNameValue);
},
openEquipDialog (item) {
this.gearToEquip = item;
},
@@ -233,7 +246,7 @@ export default {
}
},
equipItem (item) {
this.$store.dispatch('common:equip', {key: item.key, type: this.costume ? 'costume' : 'equipped'});
this.$store.dispatch('common:equip', {key: item.key, type: this.costumeMode ? 'costume' : 'equipped'});
this.gearToEquip = null;
},
changeDrawerPreference (newVal) {
@@ -260,11 +273,11 @@ export default {
this.$store.state.equipmentDrawerOpen = newState;
if (newState) {
setLocalSetting(CONSTANTS.keyConstants.EQUIPMENT_DRAWER_STATE, CONSTANTS.valueConstants.DRAWER_OPEN);
setLocalSetting(CONSTANTS.keyConstants.EQUIPMENT_DRAWER_STATE, CONSTANTS.drawerStateValues.DRAWER_OPEN);
return;
}
setLocalSetting(CONSTANTS.keyConstants.EQUIPMENT_DRAWER_STATE, CONSTANTS.valueConstants.DRAWER_CLOSED);
setLocalSetting(CONSTANTS.keyConstants.EQUIPMENT_DRAWER_STATE, CONSTANTS.drawerStateValues.DRAWER_CLOSED);
},
},
computed: {
@@ -280,10 +293,10 @@ export default {
return this.$store.state.equipmentDrawerOpen ? 1 : 0;
},
drawerPreference () {
return this.costume === true ? 'costume' : 'autoEquip';
return this.costumeMode ? 'costume' : 'autoEquip';
},
activeItems () {
return this.costume === true ? this.costumeItems : this.equippedItems;
return this.costumeMode ? this.costumeItems : this.equippedItems;
},
gearItemsByType () {
const searchText = this.searchTextThrottled;

View File

@@ -147,6 +147,7 @@
right: 100%;
height: calc(100% + 18px);
margin-top: -9px;
margin-right: 1px;
padding-top: 9px;
padding-bottom: 24px;
padding-right: 16px;

View File

@@ -211,7 +211,7 @@ export default {
// @TODO: {keyboard:false, backdrop:'static'}
} else if (after <= 30 && !this.user.flags.warnedLowHealth) {
this.$root.$emit('bv::show::modal', 'low-health');
// @TODO: {keyboard:false, backdrop:'static', controller:'UserCtrl', track:'Health Warning'}
// @TODO: {keyboard:false, backdrop:'static', controller:'UserCtrl'}
}
if (after === before) return;
if (this.user.stats.lvl === 0) return;

View File

@@ -85,6 +85,12 @@ export default {
showApiToken: false,
};
},
mounted () {
window.addEventListener('message', this.receiveMessage, false);
},
destroy () {
window.removeEventListener('message', this.receiveMessage);
},
computed: {
...mapState({user: 'user.data', credentials: 'credentials'}),
apiToken () {
@@ -92,6 +98,15 @@ export default {
},
},
methods: {
receiveMessage (eventFrom) {
if (eventFrom.origin !== 'https://www.spritely.app') return;
const creds = {
userId: this.user._id,
apiToken: this.credentials.API_TOKEN,
};
eventFrom.source.postMessage(creds, eventFrom.origin);
},
async addWebhook (url) {
let webhookInfo = {
id: uuid(),

View File

@@ -1,31 +1,19 @@
<template lang="pug">
b-modal#delete(:title="$t('deleteAccount')", :hide-footer='true' size='md')
.regular-delete(v-if='user.auth.local.email')
strong {{ $t('deleteLocalAccountText') }}
br
.row
.col-6
input.form-control(type='password', v-model='password')
br
.row
#feedback.col-12.form-group
label(for='feedbackTextArea') {{ $t('feedback') }}
textarea#feedbackTextArea.form-control(v-model='feedback')
.modal-footer
button.btn.btn-primary(@click='close()') {{ $t('neverMind') }}
button.btn.btn-danger(@click='deleteAccount()', :disabled='!password') {{ $t('deleteDo') }}
.modal-header
.social-delete(v-if='!user.auth.local.email')
h4 {{ $t('deleteAccount') }}
.modal-body
p {{ $t('deleteSocialAccountText', {magicWord: 'DELETE'}) }}
br
.row
.col-md-6
input.form-control(type='text', v-model='password')
.modal-footer
button.btn.btn-secondary(@click='close()') {{ $t('neverMind') }}
button.btn.btn-danger(:disabled='!password', @click='deleteAccount()') {{ $t('deleteDo') }}
.modal-body
br
strong(v-if='user.auth.local.email') {{ $t('deleteLocalAccountText') }}
strong(v-if='!user.auth.local.email') {{ $t('deleteSocialAccountText', {magicWord: 'DELETE'}) }}
.row.mt-3
.col-6
input.form-control(type='password', v-model='password')
.row.mt-3
#feedback.col-12.form-group
label(for='feedbackTextArea') {{ $t('feedback') }}
textarea#feedbackTextArea.form-control(v-model='feedback')
.modal-footer
button.btn.btn-primary(@click='close()') {{ $t('neverMind') }}
button.btn.btn-danger(@click='deleteAccount()', :disabled='!password') {{ $t('deleteDo') }}
</template>
<script>
@@ -44,7 +32,7 @@ export default {
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'reset');
this.$root.$emit('bv::hide::modal', 'delete');
},
async deleteAccount () {
await axios.delete('/api/v4/user', {
@@ -55,7 +43,7 @@ export default {
});
localStorage.clear();
window.location.href = '/static/home';
this.$root.$emit('bv::hide::modal', 'reset');
this.$root.$emit('bv::hide::modal', 'delete');
},
},
};

View File

@@ -22,7 +22,7 @@
)
.standard-page
div.featuredItems
.background
.background(:class="{'background-closed': closed, 'background-open': !closed }")
div.npc(:class="{'closed': closed }")
div.featured-label
span.rectangle
@@ -34,8 +34,6 @@
span.text(v-once) {{ $t('timeTravelersPopoverNoSubMobile') }}
span.rectangle
h1.mb-4.page-header(v-once) {{ $t('timeTravelers') }}
.clearfix(v-if="!closed")
div.float-right
span.dropdown-label {{ $t('sortBy') }}
@@ -164,12 +162,9 @@
height: 216px;
.background {
background: url('~assets/images/npc/#{$npc_timetravelers_flavor}/time_travelers_background.png');
background-repeat: repeat-x;
width: 100%;
height: 216px;
position: absolute;
top: 0;
@@ -180,6 +175,14 @@
justify-content: center;
align-items: center;
}
.background-open {
background: url('~assets/images/npc/#{$npc_timetravelers_flavor}/time_travelers_background.png');
height: 188px;
}
.background-closed {
background: url('~assets/images/npc/normal/time_travelers_background.png');
height: 216px;
}
.content {
display: flex;

View File

@@ -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;
}

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}`);
},
},
};

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', {

View File

@@ -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) {

View File

@@ -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');
}

View File

@@ -81,6 +81,10 @@
min-height: 556px;
}
.sortable-tasks {
word-break: break-word;
}
.sortable-tasks + .reward-items {
margin-top: 16px;
}
@@ -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);
});
},
@@ -544,6 +548,7 @@ export default {
this.quickAddText = '';
this.quickAddRows = 1;
this.createTask(tasks);
this.$refs.quickAdd.blur();
},
editTask (task) {
this.$emit('editTask', task);

View File

@@ -190,7 +190,7 @@ export default {
mounted () {
// @TODO: should we abstract the drawer state/local store to a library and mixing combo? We use a similar pattern in equipment
const spellDrawerState = getLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE);
if (spellDrawerState === CONSTANTS.valueConstants.DRAWER_CLOSED) {
if (spellDrawerState === CONSTANTS.drawerStateValues.DRAWER_CLOSED) {
this.$store.state.spellOptions.spellDrawOpen = false;
}
},
@@ -205,11 +205,11 @@ export default {
this.$store.state.spellOptions.spellDrawOpen = newState;
if (newState) {
setLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE, CONSTANTS.valueConstants.DRAWER_OPEN);
setLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE, CONSTANTS.drawerStateValues.DRAWER_OPEN);
return;
}
setLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE, CONSTANTS.valueConstants.DRAWER_CLOSED);
setLocalSetting(CONSTANTS.keyConstants.SPELL_DRAWER_STATE, CONSTANTS.drawerStateValues.DRAWER_CLOSED);
},
spellDisabled (skill) {
if (skill === 'frost' && this.user.stats.buffs.streaks) {

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
@@ -705,26 +705,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 +795,30 @@ 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;
}
// @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 +892,12 @@ export default {
async submit () {
if (this.newChecklistItem) this.addChecklistItem();
if (this.groupId) {
this.task.group.assignedUsers = this.assignedMembers;
this.task.requiresApproval = this.requiresApproval;
this.task.group.approval.required = this.requiresApproval;
}
if (this.purpose === 'create') {
if (this.challengeId) {
this.$store.dispatch('tasks:createChallengeTasks', {
@@ -906,19 +918,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);
}

View File

@@ -52,7 +52,7 @@
// @TODO: Implement new message header here when we fix the above
.new-message-row(v-if='selectedConversation.key && !user.flags.chatRevoked')
textarea(v-model='newMessage')
textarea(v-model='newMessage', @keyup.ctrl.enter='sendPrivateMessage()')
button.btn.btn-secondary(@click='sendPrivateMessage()') Send
</template>
@@ -337,12 +337,12 @@ export default {
optTextSet () {
if (!this.user.inbox.optOut) {
return {
switchDescription: this.$t('PMDisable'),
switchDescription: this.$t('PMReceive'),
popoverText: this.$t('PMEnabledOptPopoverText'),
};
}
return {
switchDescription: this.$t('PMEnable'),
switchDescription: this.$t('PMReceive'),
popoverText: this.$t('PMDisabledOptPopoverText'),
};
},

View File

@@ -3,11 +3,16 @@ const CONSTANTS = {
keyConstants: {
SPELL_DRAWER_STATE: 'spell-drawer-state',
EQUIPMENT_DRAWER_STATE: 'equipment-drawer-state',
CURRENT_EQUIPMENT_DRAWER_TAB: 'current-equipment-drawer-tab',
},
valueConstants: {
drawerStateValues: {
DRAWER_CLOSED: 'drawer-closed',
DRAWER_OPEN: 'drawer-open',
},
equipmentDrawerTabValues: {
COSTUME_TAB: 'costume-tab',
EQUIPMENT_TAB: 'equipment-tab',
},
};
function setLocalSetting (key, value) {

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 },
{
@@ -297,7 +299,7 @@ router.beforeEach(function routerGuard (to, from, next) {
name: redirectTo,
query: redirectTo === 'login' ? {
redirectTo: to.path,
} : null,
} : to.query,
});
}

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