mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 14:17:22 +01:00
Client Fixed (#9017)
* fix spacing between rewards and items * fix rewards description * rewards cost in bold * fix gp notifications * fix dailies gray text * fix cancel in task edit modal * tags: use AND not OR for filtering * fix tasksDefaults so that monthlies can be created correctly * tags: usable if no task selected, saving checklist and tags saved the one being added without requiting to press enter * remove tags from tasks when they are deleted * fix tags removal when multiple tags are deleted and fix tags editing
This commit is contained in:
@@ -54,7 +54,7 @@ export default {
|
||||
challengeId: this.brokenChallengeTask.challenge.id,
|
||||
keep: keepOption,
|
||||
});
|
||||
await this.$store.dispatch('tasks:fetchUserTasks', true);
|
||||
await this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true});
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
@editTask="editTask",
|
||||
:group='group',
|
||||
)
|
||||
template(v-if="isUser === true && type === 'reward' && activeFilter.label !== 'custom'")
|
||||
template(v-if="hasRewardsList")
|
||||
.reward-items
|
||||
shopItem(
|
||||
v-for="reward in inAppRewards",
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
.column-background(
|
||||
v-if="isUser === true",
|
||||
:class="{'initial-description': tasks[`${type}s`].length === 0}",
|
||||
:class="{'initial-description': initialColumnDescription}",
|
||||
ref="columnBackground",
|
||||
)
|
||||
.svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once)
|
||||
@@ -46,7 +46,7 @@
|
||||
height: 556px;
|
||||
}
|
||||
|
||||
.task + .reward-items {
|
||||
.task-wrapper + .reward-items {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@@ -243,6 +243,17 @@ export default {
|
||||
inAppRewards () {
|
||||
return inAppRewards(this.user);
|
||||
},
|
||||
hasRewardsList () {
|
||||
return this.isUser === true && this.type === 'reward' && this.activeFilter.label !== 'custom';
|
||||
},
|
||||
initialColumnDescription () {
|
||||
// Show the column description in the middle only if there are no elements (tasks or in app items)
|
||||
if (this.hasRewardsList) {
|
||||
if (this.inAppRewards && this.inAppRewards.length >= 0) return false;
|
||||
}
|
||||
|
||||
return this.tasks[`${this.type}s`].length === 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
taskList: {
|
||||
@@ -299,11 +310,11 @@ export default {
|
||||
const selectedTags = this.selectedTags;
|
||||
|
||||
if (selectedTags && selectedTags.length > 0) {
|
||||
const hasSelectedTag = task.tags.find(tagId => {
|
||||
return selectedTags.indexOf(tagId) !== -1;
|
||||
const hasAllSelectedTag = selectedTags.every(tagId => {
|
||||
return task.tags.indexOf(tagId) !== -1;
|
||||
});
|
||||
|
||||
if (!hasSelectedTag) return false;
|
||||
if (!hasAllSelectedTag) return false;
|
||||
}
|
||||
|
||||
// Text
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="pug">
|
||||
div
|
||||
.task-wrapper
|
||||
broken-task-modal(:brokenChallengeTask='brokenChallengeTask')
|
||||
.task(@click='castEnd($event, task)')
|
||||
approval-header(:task='task', v-if='this.task.group.id', :group='group')
|
||||
@@ -260,6 +260,7 @@ div
|
||||
margin-top: 4px;
|
||||
color: $yellow-10;
|
||||
font-style: initial;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -358,13 +359,11 @@ export default {
|
||||
return false;
|
||||
},
|
||||
controlClass () {
|
||||
const dueDate = this.dueDate || new Date();
|
||||
return this.getTaskClasses(this.task, 'control', dueDate);
|
||||
return this.getTaskClasses(this.task, 'control', this.dueDate);
|
||||
},
|
||||
contentClass () {
|
||||
const classes = [];
|
||||
const dueDate = this.dueDate || new Date();
|
||||
classes.push(this.getTaskClasses(this.task, 'content'), dueDate);
|
||||
classes.push(this.getTaskClasses(this.task, 'content', this.dueDate));
|
||||
if (this.task.type === 'reward' || this.task.type === 'habit') {
|
||||
classes.push('no-right-border');
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
slot="modal-header",
|
||||
:class="[cssClass]",
|
||||
)
|
||||
.row
|
||||
h1.col-8 {{ title }}
|
||||
.col-4
|
||||
span.cancel-task-btn(v-once, @click="cancel()") {{ $t('cancel') }}
|
||||
.clearfix
|
||||
h1.float-left {{ title }}
|
||||
.float-right.d-flex.align-items-center
|
||||
span.cancel-task-btn.mr-2(v-if="purpose !== 'create'", v-once, @click="cancel()") {{ $t('cancel') }}
|
||||
button.btn.btn-secondary(type="submit", v-once) {{ $t('save') }}
|
||||
.form-group
|
||||
label(v-once) {{ `${$t('title')}*` }}
|
||||
@@ -335,8 +335,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-task-btn {
|
||||
margin-right: .5em;
|
||||
.delete-task-btn, .cancel-task-btn {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.task-modal-footer {
|
||||
@@ -346,13 +350,7 @@
|
||||
border-top-right-radius: 8px;
|
||||
margin-top: 50px;
|
||||
|
||||
.delete-task-btn, .cancel-task-btn {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-task-btn {
|
||||
color: $red-50;
|
||||
@@ -528,7 +526,7 @@ export default {
|
||||
completed: false,
|
||||
});
|
||||
this.newChecklistItem = null;
|
||||
e.preventDefault();
|
||||
if (e) e.preventDefault();
|
||||
},
|
||||
removeChecklistItem (i) {
|
||||
this.task.checklist.splice(i, 1);
|
||||
@@ -537,6 +535,8 @@ export default {
|
||||
return moment.weekdaysMin(dayNumber);
|
||||
},
|
||||
submit () {
|
||||
if (this.newChecklistItem) this.addChecklistItem();
|
||||
|
||||
if (this.purpose === 'create') {
|
||||
if (this.challengeId) {
|
||||
this.$store.dispatch('tasks:createChallengeTasks', {
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
.input-group
|
||||
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
||||
.filter-panel(v-if="isFilterPanelOpen")
|
||||
.tags-category.d-flex(v-for="tagsType in tagsByType", v-if="tagsType.tags.length > 0", :key="tagsType.key")
|
||||
.tags-category.d-flex(
|
||||
v-for="tagsType in tagsByType",
|
||||
v-if="tagsType.tags.length > 0 || tagsType.key === 'tags'",
|
||||
:key="tagsType.key"
|
||||
)
|
||||
.tags-header
|
||||
strong(v-once) {{ $t(tagsType.key) }}
|
||||
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
|
||||
@@ -21,7 +25,7 @@
|
||||
template(v-if="editingTags && tagsType.key === 'tags'")
|
||||
.col-6(v-for="(tag, tagIndex) in tagsSnap")
|
||||
.inline-edit-input-group.tag-edit-item.input-group
|
||||
input.tag-edit-input.inline-edit-input.form-control(type="text", :value="tag.name")
|
||||
input.tag-edit-input.inline-edit-input.form-control(type="text", v-model="tag.name")
|
||||
span.input-group-btn(@click="removeTag(tagIndex)")
|
||||
.svg-icon.destroy-icon(v-html="icons.destroy")
|
||||
.col-6
|
||||
@@ -377,6 +381,7 @@ export default {
|
||||
this.tagsSnap.splice(index, 1);
|
||||
},
|
||||
saveTags () {
|
||||
if (this.newTag) this.addTag();
|
||||
this.setUser({tags: this.tagsSnap});
|
||||
this.cancelTagsEditing();
|
||||
},
|
||||
|
||||
@@ -6,23 +6,8 @@ export default {
|
||||
...mapState({notifications: 'notificationStore'}),
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
Show '+ 5 {gold_coin} 3 {silver_coin}'
|
||||
*/
|
||||
coins (money) {
|
||||
let absolute;
|
||||
let gold;
|
||||
let silver;
|
||||
absolute = Math.abs(money);
|
||||
gold = Math.floor(absolute);
|
||||
silver = Math.floor((absolute - gold) * 100);
|
||||
if (gold && silver > 0) {
|
||||
return `${gold} <span class='notification-icon shop_gold'></span> ${silver} <span class='notification-icon shop_silver'></span>`;
|
||||
} else if (gold > 0) {
|
||||
return `${gold} <span class='notification-icon shop_gold'></span>`;
|
||||
} else if (silver > 0) {
|
||||
return `${silver} <span class='notification-icon shop_silver'></span>`;
|
||||
}
|
||||
return this.round(money, 2);
|
||||
},
|
||||
crit (val) {
|
||||
let message = `${this.$t('critBonus')} ${Math.round(val)} %`;
|
||||
@@ -105,8 +90,8 @@ export default {
|
||||
}
|
||||
return sign;
|
||||
},
|
||||
round (number) {
|
||||
return Math.abs(number.toFixed(1));
|
||||
round (number, nDigits) {
|
||||
return Math.abs(number.toFixed(nDigits || 1));
|
||||
},
|
||||
notify (html, type, icon, sign) {
|
||||
this.notifications.push({
|
||||
|
||||
@@ -3,7 +3,7 @@ import axios from 'axios';
|
||||
import compact from 'lodash/compact';
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
export function fetchUserTasks (store, forceLoad = false) {
|
||||
export function fetchUserTasks (store, options = {}) {
|
||||
return loadAsyncResource({
|
||||
store,
|
||||
path: 'tasks',
|
||||
@@ -15,7 +15,7 @@ export function fetchUserTasks (store, forceLoad = false) {
|
||||
return store.dispatch('tasks:order', [response.data.data, userResource.data.tasksOrder]);
|
||||
});
|
||||
},
|
||||
forceLoad,
|
||||
forceLoad: options.forceLoad,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export function fetch (store, forceLoad = false) { // eslint-disable-line no-sha
|
||||
});
|
||||
}
|
||||
|
||||
export function set (store, changes) {
|
||||
export async function set (store, changes) {
|
||||
const user = store.state.user.data;
|
||||
|
||||
for (let key in changes) {
|
||||
@@ -30,6 +30,22 @@ export function set (store, changes) {
|
||||
});
|
||||
|
||||
user.tags = changes[key].concat(oldTags);
|
||||
|
||||
// Remove deleted tags from tasks
|
||||
const userTasksByType = (await store.dispatch('tasks:fetchUserTasks')).data; // eslint-disable-line no-await-in-loop
|
||||
|
||||
Object.keys(userTasksByType).forEach(taskType => {
|
||||
userTasksByType[taskType].forEach(task => {
|
||||
const tagsIndexesToRemove = [];
|
||||
|
||||
task.tags.forEach((tagId, tagIndex) => {
|
||||
if (user.tags.find(tag => tag.id === tagId)) return; // eslint-disable-line max-nested-callbacks
|
||||
tagsIndexesToRemove.push(tagIndex);
|
||||
});
|
||||
|
||||
tagsIndexesToRemove.forEach(i => task.tags.splice(i, 1));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setProps(user, key, changes[key]);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export function getTaskClasses (store) {
|
||||
}
|
||||
break;
|
||||
case 'content':
|
||||
if (type === 'daily' && (task.completed || !task.isDue) || type === 'todo' && task.completed) {
|
||||
if (type === 'daily' && (task.completed || !shouldDo(dueDate, task, userPreferences)) || type === 'todo' && task.completed) {
|
||||
return 'task-daily-todo-content-disabled';
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -25,8 +25,13 @@ module.exports = function taskDefaults (task = {}) {
|
||||
challenge: {
|
||||
shortName: 'None',
|
||||
},
|
||||
group: {},
|
||||
yesterDaily: true,
|
||||
group: {
|
||||
approval: {
|
||||
required: false,
|
||||
approved: false,
|
||||
requested: false,
|
||||
},
|
||||
},
|
||||
reminders: [],
|
||||
attribute: 'str',
|
||||
createdAt: new Date(), // TODO these are going to be overwritten by the server...
|
||||
@@ -74,6 +79,9 @@ module.exports = function taskDefaults (task = {}) {
|
||||
startDate: moment().startOf('day').toDate(),
|
||||
everyX: 1,
|
||||
frequency: 'weekly',
|
||||
daysOfMonth: [],
|
||||
weeksOfMonth: [],
|
||||
yesterDaily: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,8 @@ api.updateUser = {
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
|
||||
let promisesForTagsRemoval = [];
|
||||
|
||||
_.each(req.body, (val, key) => {
|
||||
let purchasable = requiresPurchase[key];
|
||||
|
||||
@@ -321,20 +323,26 @@ api.updateUser = {
|
||||
user.tags.push(Tag.sanitize(t));
|
||||
});
|
||||
|
||||
// Remove from all the tasks TODO test
|
||||
Tasks.Task.update({
|
||||
userId: user._id,
|
||||
}, {
|
||||
$pull: {
|
||||
tags: {$in: [removedTagsIds]},
|
||||
},
|
||||
}, {multi: true}).exec();
|
||||
// Remove from all the tasks
|
||||
// NOTE each tag to remove requires a query
|
||||
|
||||
promisesForTagsRemoval = removedTagsIds.map(tagId => {
|
||||
return Tasks.Task.update({
|
||||
userId: user._id,
|
||||
}, {
|
||||
$pull: {
|
||||
tags: tagId,
|
||||
},
|
||||
}, {multi: true}).exec();
|
||||
});
|
||||
} else {
|
||||
throw new NotAuthorized(res.t('messageUserOperationProtected', { operation: key }));
|
||||
}
|
||||
});
|
||||
|
||||
await user.save();
|
||||
|
||||
await Promise.all([user.save()].concat(promisesForTagsRemoval));
|
||||
|
||||
return res.respond(200, user);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user