From ccc9e6611ce79bfdf1885a9f14bcbd567c16f1d4 Mon Sep 17 00:00:00 2001 From: Alys Date: Sun, 28 Jan 2018 12:28:51 +1000 Subject: [PATCH 01/26] adjust terminology in delete To-Dos message; move similar messages together --- website/common/locales/en/tasks.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/common/locales/en/tasks.json b/website/common/locales/en/tasks.json index f12426c3a8..0a7834ba0e 100644 --- a/website/common/locales/en/tasks.json +++ b/website/common/locales/en/tasks.json @@ -1,7 +1,8 @@ { "clearCompleted": "Delete Completed", "clearCompletedDescription": "Completed To-Dos are deleted after 30 days for non-subscribers and 90 days for subscribers.", - "clearCompletedConfirm": "Are you sure you want to clear your completed todos?", + "clearCompletedConfirm": "Are you sure you want to delete your completed To-Dos?", + "sureDeleteCompletedTodos": "Are you sure you want to delete your completed To-Dos?", "lotOfToDos": "Your most recent 30 completed To-Dos are shown here. You can see older completed To-Dos from Data > Data Display Tool or Data > Export Data > User Data.", "deleteToDosExplanation": "If you click the button below, all of your completed To-Dos and archived To-Dos will be permanently deleted, except for To-Dos from active challenges and Group Plans. Export them first if you want to keep a record of them.", "addMultipleTip": "Tip: To add multiple Tasks, separate each one using a line break (Shift + Enter) and then press \"Enter.\"", @@ -121,7 +122,6 @@ "fortifyComplete": "Fortify complete!", "deleteTask": "Delete this Task", "sureDelete": "Are you sure you want to delete this task?", - "sureDeleteCompletedTodos": "Are you sure you want to delete your completed todos?", "streakCoins": "Streak Bonus!", "taskToTop": "To top", "taskToBottom": "To bottom", From 94619737e895aea3b5e34e376037ad1f530f7305 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Mon, 29 Jan 2018 08:25:07 -0700 Subject: [PATCH 02/26] Adjusted zindex of navbar to be above snackbars and modals (#9873) --- website/client/components/header/menu.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/website/client/components/header/menu.vue b/website/client/components/header/menu.vue index 6dba447b00..cbf692741d 100644 --- a/website/client/components/header/menu.vue +++ b/website/client/components/header/menu.vue @@ -144,6 +144,7 @@ div padding-right: 12.5px; height: 56px; box-shadow: 0 1px 2px 0 rgba($black, 0.24); + z-index: 1042; // To stay above snakbar notifications and modals } .navbar-header { From 12aa8a78c1f0e9fa34aafbd8eaf206fa2f82c577 Mon Sep 17 00:00:00 2001 From: Cai Lu <11846752+caillu@users.noreply.github.com> Date: Mon, 29 Jan 2018 12:48:24 -0800 Subject: [PATCH 03/26] Track sleeping in the inn with analytics (fixes #9561) (#9685) * Sleep status is tracked by analytics when toggled. * Modify test: test that analytics is called with 'sleep' event and data passed includes the user's new sleep status --- test/api/v3/integration/user/POST-user_sleep.test.js | 12 ++++++++++++ website/common/script/ops/sleep.js | 11 ++++++++++- website/server/controllers/api-v3/user.js | 3 +-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/test/api/v3/integration/user/POST-user_sleep.test.js b/test/api/v3/integration/user/POST-user_sleep.test.js index 0e9773150e..292b77e9db 100644 --- a/test/api/v3/integration/user/POST-user_sleep.test.js +++ b/test/api/v3/integration/user/POST-user_sleep.test.js @@ -1,6 +1,7 @@ import { generateUser, } from '../../../../helpers/api-integration/v3'; +import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService'; describe('POST /user/sleep', () => { let user; @@ -22,4 +23,15 @@ describe('POST /user/sleep', () => { await user.sync(); expect(user.preferences.sleep).to.be.false; }); + + it('sends sleep status to analytics service', async () => { + sandbox.spy(analytics, 'track'); + + await user.post('/user/sleep'); + await user.sync(); + expect(analytics.track).to.be.calledOnce; + expect(analytics.track).to.be.calledWith('sleep', sandbox.match.has('status', user.preferences.sleep)); + + sandbox.restore(); + }); }); diff --git a/website/common/script/ops/sleep.js b/website/common/script/ops/sleep.js index c84994c294..e80f766b0a 100644 --- a/website/common/script/ops/sleep.js +++ b/website/common/script/ops/sleep.js @@ -1,5 +1,14 @@ -module.exports = function sleep (user) { +module.exports = function sleep (user, req = {}, analytics) { user.preferences.sleep = !user.preferences.sleep; + if (analytics) { + analytics.track('sleep', { + uuid: user._id, + status: user.preferences.sleep, + category: 'behavior', + headers: req.headers, + }); + } + return [user.preferences.sleep]; }; diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js index 0f7d1b1017..8b460e486b 100644 --- a/website/server/controllers/api-v3/user.js +++ b/website/server/controllers/api-v3/user.js @@ -729,7 +729,7 @@ api.sleep = { url: '/user/sleep', async handler (req, res) { let user = res.locals.user; - let sleepRes = common.ops.sleep(user); + let sleepRes = common.ops.sleep(user, req, res.analytics); await user.save(); res.respond(200, ...sleepRes); }, @@ -787,7 +787,6 @@ api.buy = { let quantity = 1; if (req.body.quantity) quantity = req.body.quantity; req.quantity = quantity; - buyRes = common.ops.buy(user, req, res.analytics); await user.save(); From 70da5940a765cb200a88c62e156893efb3742b8b Mon Sep 17 00:00:00 2001 From: Alys Date: Tue, 30 Jan 2018 06:52:28 +1000 Subject: [PATCH 04/26] clarify that "Leave" refers to guild/party; fix pluralisation in keep/remove challenge tasks (#9706) * change "Keep/Remove It" to "Keep/Remove Them" when asking about all challenge tasks while leaving a challenge * change "Leave" button on groups to "Leave Guild" or "Leave Party" This is because the button is underneath the challenges so this clarifies that it is referring to the group, not a challenge. * change "Keep/Remove Them" to "Keep/Remove Tasks" --- .../client/components/challenges/leaveChallengeModal.vue | 4 ++-- website/client/components/groups/group.vue | 2 +- website/common/locales/en/challenge.json | 4 ++-- website/common/locales/en/groups.json | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/website/client/components/challenges/leaveChallengeModal.vue b/website/client/components/challenges/leaveChallengeModal.vue index 0f889cf6d0..0925a4e1d0 100644 --- a/website/client/components/challenges/leaveChallengeModal.vue +++ b/website/client/components/challenges/leaveChallengeModal.vue @@ -3,8 +3,8 @@ .modal-body h2 {{ $t('confirmKeepChallengeTasks') }} div - button.btn.btn-primary(@click='leaveChallenge("keep")') {{ $t('keepIt') }} - button.btn.btn-danger(@click='leaveChallenge("remove-all")') {{ $t('removeIt') }} + button.btn.btn-primary(@click='leaveChallenge("keep")') {{ $t('keepThem') }} + button.btn.btn-danger(@click='leaveChallenge("remove-all")') {{ $t('removeThem') }} + + diff --git a/website/client/components/groups/group.vue b/website/client/components/groups/group.vue index 1070f90e35..28b934b5ed 100644 --- a/website/client/components/groups/group.vue +++ b/website/client/components/groups/group.vue @@ -37,10 +37,7 @@ button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }} .col-6 button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }} - .row.community-guidelines(v-if='!communityGuidelinesAccepted') - div.col-8(v-once, v-html="$t('communityGuidelinesIntro')") - div.col-4 - button.btn.btn-info(@click='acceptCommunityGuidelines()', v-once) {{ $t('acceptCommunityGuidelines') }} + community-guidelines .row .col-12.hr chat-message(:chat.sync='group.chat', :group-id='group._id', group-name='group.name') @@ -229,19 +226,6 @@ .chat-row { margin-top: 2em; - .community-guidelines { - background-color: rgba(135, 129, 144, 0.84); - padding: 1em; - color: $white; - position: absolute; - top: 0; - height: 150px; - padding-top: 3em; - margin-top: 2.3em; - width: 100%; - border-radius: 4px; - } - .new-message-row { position: relative; } @@ -311,6 +295,7 @@ import groupChallenges from '../challenges/groupChallenges'; import groupGemsModal from 'client/components/groups/groupGemsModal'; import questSidebarSection from 'client/components/groups/questSidebarSection'; import markdownDirective from 'client/directives/markdown'; +import communityGuidelines from './communityGuidelines'; import deleteIcon from 'assets/svg/delete.svg'; import copyIcon from 'assets/svg/copy.svg'; @@ -341,6 +326,7 @@ export default { questDetailsModal, groupGemsModal, questSidebarSection, + communityGuidelines, }, directives: { markdown: markdownDirective, @@ -381,9 +367,6 @@ export default { }, computed: { ...mapState({user: 'user.data'}), - communityGuidelinesAccepted () { - return this.user.flags.communityGuidelinesAccepted; - }, partyStore () { return this.$store.state.party; }, @@ -454,9 +437,6 @@ export default { }, }, methods: { - acceptCommunityGuidelines () { - this.$store.dispatch('user:set', {'flags.communityGuidelinesAccepted': true}); - }, load () { if (this.isParty) { this.searchId = 'party'; diff --git a/website/client/components/groups/tavern.vue b/website/client/components/groups/tavern.vue index 52a8757b21..e77106daf4 100644 --- a/website/client/components/groups/tavern.vue +++ b/website/client/components/groups/tavern.vue @@ -20,10 +20,7 @@ .col-6 button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }} - .row.community-guidelines(v-if='!communityGuidelinesAccepted') - div.col-8(v-once, v-html="$t('communityGuidelinesIntro')") - div.col-4 - button.btn.btn-info(@click='acceptCommunityGuidelines()', v-once) {{ $t('acceptCommunityGuidelines') }} + community-guidelines .row .hr.col-12 @@ -148,19 +145,6 @@ .chat-row { position: relative; - .community-guidelines { - background-color: rgba(135, 129, 144, 0.84); - padding: 1em; - color: $white; - position: absolute; - top: 0; - height: 150px; - padding-top: 3em; - margin-top: 2.3em; - width: 100%; - border-radius: 4px; - } - textarea { height: 150px; width: 100%; @@ -360,6 +344,7 @@ import { mapState } from 'client/libs/store'; import { TAVERN_ID } from '../../../common/script/constants'; import chatMessage from '../chat/chatMessages'; import autocomplete from '../chat/autoComplete'; +import communityGuidelines from './communityGuidelines'; import gemIcon from 'assets/svg/gem.svg'; import questIcon from 'assets/svg/quest.svg'; @@ -384,6 +369,7 @@ export default { components: { chatMessage, autocomplete, + communityGuidelines, }, data () { return { @@ -502,9 +488,6 @@ export default { }, computed: { ...mapState({user: 'user.data'}), - communityGuidelinesAccepted () { - return this.user.flags.communityGuidelinesAccepted; - }, }, async mounted () { this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID}); @@ -542,9 +525,6 @@ export default { selectedAutocomplete (newText) { this.newMessage = newText; }, - acceptCommunityGuidelines () { - this.$store.dispatch('user:set', {'flags.communityGuidelinesAccepted': true}); - }, toggleSleep () { this.user.preferences.sleep = !this.user.preferences.sleep; this.$store.dispatch('user:sleep'); From 25cecf298f9f2e2b350754567c13fa4b71c7246f Mon Sep 17 00:00:00 2001 From: James Hwang Date: Mon, 29 Jan 2018 13:30:05 -0800 Subject: [PATCH 17/26] Fixed formatting of modal strings to correctly display message #9818 (#9862) --- website/client/components/achievements/contributor.vue | 1 + website/common/locales/en/contrib.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/website/client/components/achievements/contributor.vue b/website/client/components/achievements/contributor.vue index 90a6f5eda3..eba4be3f8b 100644 --- a/website/client/components/achievements/contributor.vue +++ b/website/client/components/achievements/contributor.vue @@ -5,6 +5,7 @@ achievement-avatar.avatar .col-6.offset-3.text-center | {{ $t('contribModal', {name: user.profile.name, level: user.contributor.level}) }} + br a(:href="$t('conRewardsURL')", target='_blank') {{ $t('contribLink') }} br button.btn.btn-primary(style='margin-top:1em' @click='close()') {{ $t('huzzah') }} diff --git a/website/common/locales/en/contrib.json b/website/common/locales/en/contrib.json index c0df17906c..4977277a27 100644 --- a/website/common/locales/en/contrib.json +++ b/website/common/locales/en/contrib.json @@ -29,8 +29,8 @@ "heroicText": "The Heroic tier contains Habitica staff and staff-level contributors. If you have this title, you were appointed to it (or hired!).", "npcText": "NPCs backed Habitica's Kickstarter at the highest tier. You can find their avatars watching over site features!", "modalContribAchievement": "Contributor Achievement!", - "contribModal": "<%= name %>, you awesome person! You're now a tier <%= level %> contributor for helping Habitica. See", - "contribLink": "what prizes you've earned for your contribution!", + "contribModal": "<%= name %>, you awesome person! You're now a tier <%= level %> contributor for helping Habitica.", + "contribLink": "See what prizes you've earned for your contribution!", "contribName": "Contributor", "contribText": "Has contributed to Habitica, whether via code, art, music, writing, or other methods. To learn more, join the Aspiring Legends Guild!", "readMore": "Read More", From 5d4e1362bb328d0078407f0ebe1189707eef51cf Mon Sep 17 00:00:00 2001 From: Cassidy Pignatello Date: Mon, 29 Jan 2018 16:32:21 -0500 Subject: [PATCH 18/26] updates Orb of Rebirth description to more accurately list its effects (#9894) --- website/common/locales/en/rebirth.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/common/locales/en/rebirth.json b/website/common/locales/en/rebirth.json index be8e627003..fa49b57f2b 100644 --- a/website/common/locales/en/rebirth.json +++ b/website/common/locales/en/rebirth.json @@ -21,7 +21,7 @@ "rebirthOrb": "Used an Orb of Rebirth to start over after attaining Level <%= level %>.", "rebirthOrb100": "Used an Orb of Rebirth to start over after attaining Level 100 or higher.", "rebirthOrbNoLevel": "Used an Orb of Rebirth to start over.", - "rebirthPop": "Instantly restart your character at Level 1 while retaining achievements, collectibles, equipment, and tasks with history. This will take effect immediately.", + "rebirthPop": "Instantly restart your character as a Level 1 Warrior while retaining achievements, collectibles, and equipment. Your tasks and their history will remain but they will be reset to yellow. Your streaks will be removed except from challenge tasks. Your Gold, Experience, Mana, and the effects of all Skills will be removed. All of this will take effect immediately. For more information, see the wiki's Orb of Rebirth page.", "rebirthName": "Orb of Rebirth", "reborn": "Reborn, max level <%= reLevel %>", "confirmReborn": "Are you sure?", From 47c2a3a21a0afc0eb2a2c9718782191491ebd332 Mon Sep 17 00:00:00 2001 From: Alys Date: Tue, 30 Jan 2018 07:43:59 +1000 Subject: [PATCH 19/26] prevent "Zero-day streak" giving a 21-day streak achievement + tests - fixes #2578 (#9688) * prevent "Zero-day streak" giving a 21-day streak achievement - fixes #2578 * add tests for streak achievements * remove .only from set of tests * refactor(test): fix linting --- test/common/ops/scoreTask.test.js | 65 +++++++++++++++++++++++--- website/common/script/ops/scoreTask.js | 4 +- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/test/common/ops/scoreTask.test.js b/test/common/ops/scoreTask.test.js index 624a664a5b..bbd44aff79 100644 --- a/test/common/ops/scoreTask.test.js +++ b/test/common/ops/scoreTask.test.js @@ -74,13 +74,6 @@ describe('shared.ops.scoreTask', () => { } }); - it('checks that the streak parameters affects the score', () => { - let task = generateDaily({ userId: ref.afterUser._id, text: 'task to check streak' }); - scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false }); - scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false }); - expect(task.streak).to.eql(2); - }); - it('completes when the task direction is up', () => { let task = generateTodo({ userId: ref.afterUser._id, text: 'todo to complete', cron: false }); scoreTask({ user: ref.afterUser, task, direction: 'up' }); @@ -123,6 +116,64 @@ describe('shared.ops.scoreTask', () => { }); }); + it('checks that the streak parameters affects the score', () => { + let task = generateDaily({ userId: ref.afterUser._id, text: 'task to check streak' }); + scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false }); + scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false }); + expect(task.streak).to.eql(2); + }); + + describe('verifies that 21-day streak achievements are given/removed correctly', () => { + let initialStreakCount = 20; // 1 before the streak achievement is awarded + beforeEach(() => { + ref = beforeAfter(); + }); + + it('awards the first streak achievement', () => { + let task = generateDaily({ userId: ref.afterUser._id, text: 'some daily', streak: initialStreakCount }); + scoreTask({ user: ref.afterUser, task, direction: 'up' }); + expect(ref.afterUser.achievements.streak).to.equal(1); + }); + + it('increments the streak achievement for a second streak', () => { + let task1 = generateDaily({ userId: ref.afterUser._id, text: 'first daily', streak: initialStreakCount }); + scoreTask({ user: ref.afterUser, task: task1, direction: 'up' }); + let task2 = generateDaily({ userId: ref.afterUser._id, text: 'second daily', streak: initialStreakCount }); + scoreTask({ user: ref.afterUser, task: task2, direction: 'up' }); + expect(ref.afterUser.achievements.streak).to.equal(2); + }); + + it('removes the first streak achievement when unticking a Daily', () => { + let task = generateDaily({ userId: ref.afterUser._id, text: 'some daily', streak: initialStreakCount }); + scoreTask({ user: ref.afterUser, task, direction: 'up' }); + scoreTask({ user: ref.afterUser, task, direction: 'down' }); + expect(ref.afterUser.achievements.streak).to.equal(0); + }); + + it('decrements a multiple streak achievement when unticking a Daily', () => { + let task1 = generateDaily({ userId: ref.afterUser._id, text: 'first daily', streak: initialStreakCount }); + scoreTask({ user: ref.afterUser, task: task1, direction: 'up' }); + let task2 = generateDaily({ userId: ref.afterUser._id, text: 'second daily', streak: initialStreakCount }); + scoreTask({ user: ref.afterUser, task: task2, direction: 'up' }); + scoreTask({ user: ref.afterUser, task: task2, direction: 'down' }); + expect(ref.afterUser.achievements.streak).to.equal(1); + }); + + it('does not give a streak achievement for a streak of zero', () => { + let task = generateDaily({ userId: ref.afterUser._id, text: 'some daily', streak: -1 }); + scoreTask({ user: ref.afterUser, task, direction: 'up' }); + expect(ref.afterUser.achievements.streak).to.be.undefined; + }); + + it('does not remove a streak achievement when unticking a Daily gives a streak of zero', () => { + let task1 = generateDaily({ userId: ref.afterUser._id, text: 'first daily', streak: initialStreakCount }); + scoreTask({ user: ref.afterUser, task: task1, direction: 'up' }); + let task2 = generateDaily({ userId: ref.afterUser._id, text: 'second daily', streak: 1 }); + scoreTask({ user: ref.afterUser, task: task2, direction: 'down' }); + expect(ref.afterUser.achievements.streak).to.equal(1); + }); + }); + describe('scores', () => { let options = {}; let habit; diff --git a/website/common/script/ops/scoreTask.js b/website/common/script/ops/scoreTask.js index 1bddbc254f..b199d49737 100644 --- a/website/common/script/ops/scoreTask.js +++ b/website/common/script/ops/scoreTask.js @@ -238,14 +238,14 @@ module.exports = function scoreTask (options = {}, req = {}) { if (direction === 'up') { task.streak += 1; // Give a streak achievement when the streak is a multiple of 21 - if (task.streak % 21 === 0) { + if (task.streak !== 0 && task.streak % 21 === 0) { user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1; if (user.addNotification) user.addNotification('STREAK_ACHIEVEMENT'); } task.completed = true; } else if (direction === 'down') { // Remove a streak achievement if streak was a multiple of 21 and the daily was undone - if (task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0; + if (task.streak !== 0 && task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0; task.streak -= 1; task.completed = false; } From ae27ae00903e9e1a53e668515067d91d42aff718 Mon Sep 17 00:00:00 2001 From: Alexey Pyltsyn Date: Mon, 29 Jan 2018 21:44:22 +0000 Subject: [PATCH 20/26] Improved Party page UI (#9892) --- website/client/components/groups/group.vue | 38 ++++++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/website/client/components/groups/group.vue b/website/client/components/groups/group.vue index 28b934b5ed..570592f240 100644 --- a/website/client/components/groups/group.vue +++ b/website/client/components/groups/group.vue @@ -19,7 +19,7 @@ .svg-icon.shield(v-html="icons.silverGuildBadgeIcon", v-if='group.memberCount > 100 && group.memberCount < 999') .svg-icon.shield(v-html="icons.bronzeGuildBadgeIcon", v-if='group.memberCount < 100') span.number {{ group.memberCount | abbrNum }} - div(v-once) {{ $t('memberList') }} + div.member-list(v-once) {{ $t('memberList') }} .col-4(v-if='!isParty') .item-with-icon(@click='showGroupGems()') .svg-icon.gem(v-html="icons.gem") @@ -31,11 +31,11 @@ .row.new-message-row textarea(:placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')", v-model='newMessage', @keydown='updateCarretPosition', @keyup.ctrl.enter='sendMessage()') autocomplete(:text='newMessage', v-on:select="selectedAutocomplete", :coords='coords', :chat='group.chat') - .row - .col-6 + .row.chat-actions + .col-6.chat-receive-actions button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }} button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }} - .col-6 + .col-6.chat-send-actions button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }} community-guidelines .row @@ -43,8 +43,7 @@ chat-message(:chat.sync='group.chat', :group-id='group._id', group-name='group.name') .col-12.col-sm-4.sidebar .row(:class='{"guild-background": !isParty}') - .col-6 - .col-6 + .col-12 .button-container button.btn.btn-success(class='btn-success', v-if='isLeader && !group.purchased.active', @click='upgradeGroup()') | {{ $t('upgrade') }} @@ -143,7 +142,7 @@ .svg-icon.shield, .svg-icon.gem { width: 28px; - height: 28px; + height: auto; margin: 0 auto; display: inline-block; vertical-align: bottom; @@ -154,6 +153,10 @@ font-size: 22px; font-weight: bold; } + + .member-list { + margin-top: .5em; + } } .item-with-icon:hover { @@ -163,6 +166,7 @@ .sidebar { background-color: $gray-600; padding-bottom: 2em; + padding-top: 2.8em; } .card { @@ -229,6 +233,26 @@ .new-message-row { position: relative; } + + .chat-actions { + margin-top: 1em; + + .chat-receive-actions { + padding-left: 0; + + button { + margin-bottom: 1em; + + &:not(:last-child) { + margin-right: 1em; + } + } + } + + .chat-send-actions { + padding-right: 0; + } + } } span.action { From 1dc558ddbacf42390f46e8aa9792531ffc36e243 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 29 Jan 2018 23:23:40 +0100 Subject: [PATCH 21/26] prevent ex-participants appearing in challenge export file - Fix #9844 (#9846) * possible fix for 9844 * fix typo in challengeModal file * remove lines for empty users --- .../components/challenges/challengeModal.vue | 7 +++---- website/server/controllers/api-v3/challenges.js | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/website/client/components/challenges/challengeModal.vue b/website/client/components/challenges/challengeModal.vue index 8f39632bad..e51a043e81 100644 --- a/website/client/components/challenges/challengeModal.vue +++ b/website/client/components/challenges/challengeModal.vue @@ -53,10 +53,9 @@ input(type='number', :min='minPrize', :max='maxPrize', v-model="workingChallenge.prize") .row.footer-wrap .col-12.text-center.submit-button-wrapper - .alert.alert-warning(v-if='insufficientGemsForTavernChallenge') - You do not have enough gems to create a Tavern challenge - // @TODO if buy gems button is added, add analytics tracking to it - // see https://github.com/HabitRPG/habitica/blob/develop/website/views/options/social/challenges.jade#L134 + .alert.alert-warning(v-if='insufficientGemsForTavernChallenge') You do not have enough gems to create a Tavern challenge + // @TODO if buy gems button is added, add analytics tracking to it + // see https://github.com/HabitRPG/habitica/blob/develop/website/views/options/social/challenges.jade#L134 button.btn.btn-primary(v-if='creating && !cloning', @click='createChallenge()', :disabled='loading') {{$t('createChallengeAddTasks')}} button.btn.btn-primary(v-once, v-if='cloning', @click='createChallenge()', :disabled='loading') {{$t('createChallengeCloneTasks')}} button.btn.btn-primary(v-once, v-if='!creating && !cloning', @click='updateChallenge()') {{$t('updateChallenge')}} diff --git a/website/server/controllers/api-v3/challenges.js b/website/server/controllers/api-v3/challenges.js index 2d58eda175..aa5a4852c7 100644 --- a/website/server/controllers/api-v3/challenges.js +++ b/website/server/controllers/api-v3/challenges.js @@ -592,13 +592,12 @@ api.exportChallengeCsv = { let resArray = members.map(member => [member._id, member.profile.name]); - // We assume every user in the challenge as at least some data so we can say that members[0] tasks will be at tasks [0] let lastUserId; let index = -1; tasks.forEach(task => { - if (task.userId !== lastUserId) { - lastUserId = task.userId; + while (task.userId !== lastUserId) { index++; + lastUserId = resArray[index][0]; // resArray[index][0] is an user id } const streak = task.streak || 0; @@ -611,8 +610,18 @@ api.exportChallengeCsv = { return result.concat(array); }, []).sort(); resArray.unshift(['UUID', 'name']); + _.times(challengeTasks.length, () => resArray[0].push('Task', 'Value', 'Notes', 'Streak')); + // Remove lines for users without tasks info + resArray = resArray.filter((line) => { + if (line.length === 2) { // only user data ([id, profile name]), no task data + return false; + } + + return true; + }); + res.set({ 'Content-Type': 'text/csv', 'Content-disposition': `attachment; filename=${challengeId}.csv`, From ccf8e0b3209d0184ce31f124af03af9ba975dc3e Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Mon, 29 Jan 2018 21:34:18 -0600 Subject: [PATCH 22/26] Removed gitattributes --- .gitattributes | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index c89d637333..0000000000 --- a/.gitattributes +++ /dev/null @@ -1,21 +0,0 @@ -*.css text -*.html text -*.js text -*.json text -*.md text -*.sh text -*.scss text -*.svg text -*.txt text -*.vue text -*.yml text - -*.gif binary -*.ico binary -*.mp3 binary -*.mp4 binary -*.ogg binary -*.png binary -*.zip binary - -* text eol=lf \ No newline at end of file From 4fe6c8db6427c41c7341c34850dc189b479975f8 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Tue, 30 Jan 2018 08:23:20 -0700 Subject: [PATCH 23/26] Clone challenges api (#9684) * Added clone api * Added new clone UI * Fixed challenge clone * Fixed lint and added mongo toObject * Removed clone field, fixed type, fixed challenge task query * Auto selected group * Accounted for group balance when creating challenge * Added check for if user is leader of guild * Added leader existence check * Added fix for leader and prizecost equal to --- .../POST_challenges_id_clone.test.js | 43 ++++ .../components/challenges/challengeDetail.vue | 37 +--- .../components/challenges/challengeModal.vue | 55 ++++- website/client/store/actions/challenges.js | 7 + .../server/controllers/api-v3/challenges.js | 206 ++++++++++++------ 5 files changed, 244 insertions(+), 104 deletions(-) create mode 100644 test/api/v3/integration/challenges/POST_challenges_id_clone.test.js diff --git a/test/api/v3/integration/challenges/POST_challenges_id_clone.test.js b/test/api/v3/integration/challenges/POST_challenges_id_clone.test.js new file mode 100644 index 0000000000..04e12049cd --- /dev/null +++ b/test/api/v3/integration/challenges/POST_challenges_id_clone.test.js @@ -0,0 +1,43 @@ +import { + generateUser, + generateGroup, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /challenges/:challengeId/clone', () => { + it('clones a challenge', async () => { + const user = await generateUser({balance: 10}); + const group = await generateGroup(user); + + const name = 'Test Challenge'; + const shortName = 'TC Label'; + const description = 'Test Description'; + const prize = 1; + + const challenge = await user.post('/challenges', { + group: group._id, + name, + shortName, + description, + prize, + }); + const challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + up: false, + down: true, + notes: 1976, + }); + + const cloneChallengeResponse = await user.post(`/challenges/${challenge._id}/clone`, { + group: group._id, + name: `${name} cloned`, + shortName, + description, + prize, + }); + + expect(cloneChallengeResponse.clonedTasks[0].text).to.eql(challengeTask.text); + expect(cloneChallengeResponse.clonedTasks[0]._id).to.not.eql(challengeTask._id); + expect(cloneChallengeResponse.clonedTasks[0].challenge.id).to.eql(cloneChallengeResponse.clonedChallenge._id); + }); +}); diff --git a/website/client/components/challenges/challengeDetail.vue b/website/client/components/challenges/challengeDetail.vue index 01f07bcb6d..efa05268ae 100644 --- a/website/client/components/challenges/challengeDetail.vue +++ b/website/client/components/challenges/challengeDetail.vue @@ -1,6 +1,6 @@ @@ -236,11 +236,11 @@ div margin-right: 24px; } - &:hover /deep/ .svg-icon { + &:hover /deep/ .top-menu-icon.svg-icon { color: $white; } - & /deep/ .svg-icon { + & /deep/ .top-menu-icon.svg-icon { color: $header-color; vertical-align: bottom; display: inline-block; diff --git a/website/client/components/header/messageCount.vue b/website/client/components/header/messageCount.vue index 7163808e3e..8ad65de4d1 100644 --- a/website/client/components/header/messageCount.vue +++ b/website/client/components/header/messageCount.vue @@ -1,5 +1,7 @@ \ No newline at end of file diff --git a/website/client/components/header/notifications/base.vue b/website/client/components/header/notifications/base.vue new file mode 100644 index 0000000000..fc6bcbaa68 --- /dev/null +++ b/website/client/components/header/notifications/base.vue @@ -0,0 +1,162 @@ + + + + + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/cardReceived.vue b/website/client/components/header/notifications/cardReceived.vue new file mode 100644 index 0000000000..0762f6e09a --- /dev/null +++ b/website/client/components/header/notifications/cardReceived.vue @@ -0,0 +1,35 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/challengeInvitation.vue b/website/client/components/header/notifications/challengeInvitation.vue new file mode 100644 index 0000000000..97083da552 --- /dev/null +++ b/website/client/components/header/notifications/challengeInvitation.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/website/client/components/header/notifications/groupTaskApproval.vue b/website/client/components/header/notifications/groupTaskApproval.vue new file mode 100644 index 0000000000..305d1bd799 --- /dev/null +++ b/website/client/components/header/notifications/groupTaskApproval.vue @@ -0,0 +1,72 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/groupTaskApproved.vue b/website/client/components/header/notifications/groupTaskApproved.vue new file mode 100644 index 0000000000..35982f3ef9 --- /dev/null +++ b/website/client/components/header/notifications/groupTaskApproved.vue @@ -0,0 +1,27 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/groupTaskNeedsWork.vue b/website/client/components/header/notifications/groupTaskNeedsWork.vue new file mode 100644 index 0000000000..068e479f72 --- /dev/null +++ b/website/client/components/header/notifications/groupTaskNeedsWork.vue @@ -0,0 +1,27 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/guildInvitation.vue b/website/client/components/header/notifications/guildInvitation.vue new file mode 100644 index 0000000000..6920e36392 --- /dev/null +++ b/website/client/components/header/notifications/guildInvitation.vue @@ -0,0 +1,64 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/newChatMessage.vue b/website/client/components/header/notifications/newChatMessage.vue new file mode 100644 index 0000000000..42c86248a4 --- /dev/null +++ b/website/client/components/header/notifications/newChatMessage.vue @@ -0,0 +1,46 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/newInboxMessage.vue b/website/client/components/header/notifications/newInboxMessage.vue new file mode 100644 index 0000000000..05f76b2f15 --- /dev/null +++ b/website/client/components/header/notifications/newInboxMessage.vue @@ -0,0 +1,28 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/newMysteryItems.vue b/website/client/components/header/notifications/newMysteryItems.vue new file mode 100644 index 0000000000..763b848b11 --- /dev/null +++ b/website/client/components/header/notifications/newMysteryItems.vue @@ -0,0 +1,33 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/newStuff.vue b/website/client/components/header/notifications/newStuff.vue new file mode 100644 index 0000000000..a479748413 --- /dev/null +++ b/website/client/components/header/notifications/newStuff.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/partyInvitation.vue b/website/client/components/header/notifications/partyInvitation.vue new file mode 100644 index 0000000000..a93d277970 --- /dev/null +++ b/website/client/components/header/notifications/partyInvitation.vue @@ -0,0 +1,42 @@ + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/questInvitation.vue b/website/client/components/header/notifications/questInvitation.vue new file mode 100644 index 0000000000..ce8e6b9106 --- /dev/null +++ b/website/client/components/header/notifications/questInvitation.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/website/client/components/header/notifications/unallocatedStatsPoints.vue b/website/client/components/header/notifications/unallocatedStatsPoints.vue new file mode 100644 index 0000000000..8841750fbb --- /dev/null +++ b/website/client/components/header/notifications/unallocatedStatsPoints.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/website/client/components/header/notificationsDropdown.vue b/website/client/components/header/notificationsDropdown.vue index 2b29937a91..a2849fa0bb 100644 --- a/website/client/components/header/notificationsDropdown.vue +++ b/website/client/components/header/notificationsDropdown.vue @@ -1,289 +1,254 @@ diff --git a/website/client/components/static/home.vue b/website/client/components/static/home.vue index 8f20858108..2273ed6884 100644 --- a/website/client/components/static/home.vue +++ b/website/client/components/static/home.vue @@ -1,5 +1,5 @@ + + diff --git a/website/client/components/static/overview.vue b/website/client/components/static/overview.vue index c6698d7865..cfd14c700b 100644 --- a/website/client/components/static/overview.vue +++ b/website/client/components/static/overview.vue @@ -1,5 +1,5 @@ - +