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,
|
challengeId: this.brokenChallengeTask.challenge.id,
|
||||||
keep: keepOption,
|
keep: keepOption,
|
||||||
});
|
});
|
||||||
await this.$store.dispatch('tasks:fetchUserTasks', true);
|
await this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true});
|
||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
@editTask="editTask",
|
@editTask="editTask",
|
||||||
:group='group',
|
:group='group',
|
||||||
)
|
)
|
||||||
template(v-if="isUser === true && type === 'reward' && activeFilter.label !== 'custom'")
|
template(v-if="hasRewardsList")
|
||||||
.reward-items
|
.reward-items
|
||||||
shopItem(
|
shopItem(
|
||||||
v-for="reward in inAppRewards",
|
v-for="reward in inAppRewards",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
.column-background(
|
.column-background(
|
||||||
v-if="isUser === true",
|
v-if="isUser === true",
|
||||||
:class="{'initial-description': tasks[`${type}s`].length === 0}",
|
:class="{'initial-description': initialColumnDescription}",
|
||||||
ref="columnBackground",
|
ref="columnBackground",
|
||||||
)
|
)
|
||||||
.svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once)
|
.svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once)
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
height: 556px;
|
height: 556px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task + .reward-items {
|
.task-wrapper + .reward-items {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +243,17 @@ export default {
|
|||||||
inAppRewards () {
|
inAppRewards () {
|
||||||
return inAppRewards(this.user);
|
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: {
|
watch: {
|
||||||
taskList: {
|
taskList: {
|
||||||
@@ -299,11 +310,11 @@ export default {
|
|||||||
const selectedTags = this.selectedTags;
|
const selectedTags = this.selectedTags;
|
||||||
|
|
||||||
if (selectedTags && selectedTags.length > 0) {
|
if (selectedTags && selectedTags.length > 0) {
|
||||||
const hasSelectedTag = task.tags.find(tagId => {
|
const hasAllSelectedTag = selectedTags.every(tagId => {
|
||||||
return selectedTags.indexOf(tagId) !== -1;
|
return task.tags.indexOf(tagId) !== -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasSelectedTag) return false;
|
if (!hasAllSelectedTag) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
div
|
.task-wrapper
|
||||||
broken-task-modal(:brokenChallengeTask='brokenChallengeTask')
|
broken-task-modal(:brokenChallengeTask='brokenChallengeTask')
|
||||||
.task(@click='castEnd($event, task)')
|
.task(@click='castEnd($event, task)')
|
||||||
approval-header(:task='task', v-if='this.task.group.id', :group='group')
|
approval-header(:task='task', v-if='this.task.group.id', :group='group')
|
||||||
@@ -260,6 +260,7 @@ div
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
color: $yellow-10;
|
color: $yellow-10;
|
||||||
font-style: initial;
|
font-style: initial;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -358,13 +359,11 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
controlClass () {
|
controlClass () {
|
||||||
const dueDate = this.dueDate || new Date();
|
return this.getTaskClasses(this.task, 'control', this.dueDate);
|
||||||
return this.getTaskClasses(this.task, 'control', dueDate);
|
|
||||||
},
|
},
|
||||||
contentClass () {
|
contentClass () {
|
||||||
const classes = [];
|
const classes = [];
|
||||||
const dueDate = this.dueDate || new Date();
|
classes.push(this.getTaskClasses(this.task, 'content', this.dueDate));
|
||||||
classes.push(this.getTaskClasses(this.task, 'content'), dueDate);
|
|
||||||
if (this.task.type === 'reward' || this.task.type === 'habit') {
|
if (this.task.type === 'reward' || this.task.type === 'habit') {
|
||||||
classes.push('no-right-border');
|
classes.push('no-right-border');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
slot="modal-header",
|
slot="modal-header",
|
||||||
:class="[cssClass]",
|
:class="[cssClass]",
|
||||||
)
|
)
|
||||||
.row
|
.clearfix
|
||||||
h1.col-8 {{ title }}
|
h1.float-left {{ title }}
|
||||||
.col-4
|
.float-right.d-flex.align-items-center
|
||||||
span.cancel-task-btn(v-once, @click="cancel()") {{ $t('cancel') }}
|
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') }}
|
button.btn.btn-secondary(type="submit", v-once) {{ $t('save') }}
|
||||||
.form-group
|
.form-group
|
||||||
label(v-once) {{ `${$t('title')}*` }}
|
label(v-once) {{ `${$t('title')}*` }}
|
||||||
@@ -335,8 +335,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-task-btn {
|
.delete-task-btn, .cancel-task-btn {
|
||||||
margin-right: .5em;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover, &:focus, &:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-modal-footer {
|
.task-modal-footer {
|
||||||
@@ -346,13 +350,7 @@
|
|||||||
border-top-right-radius: 8px;
|
border-top-right-radius: 8px;
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
|
|
||||||
.delete-task-btn, .cancel-task-btn {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover, &:focus, &:active {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-task-btn {
|
.delete-task-btn {
|
||||||
color: $red-50;
|
color: $red-50;
|
||||||
@@ -528,7 +526,7 @@ export default {
|
|||||||
completed: false,
|
completed: false,
|
||||||
});
|
});
|
||||||
this.newChecklistItem = null;
|
this.newChecklistItem = null;
|
||||||
e.preventDefault();
|
if (e) e.preventDefault();
|
||||||
},
|
},
|
||||||
removeChecklistItem (i) {
|
removeChecklistItem (i) {
|
||||||
this.task.checklist.splice(i, 1);
|
this.task.checklist.splice(i, 1);
|
||||||
@@ -537,6 +535,8 @@ export default {
|
|||||||
return moment.weekdaysMin(dayNumber);
|
return moment.weekdaysMin(dayNumber);
|
||||||
},
|
},
|
||||||
submit () {
|
submit () {
|
||||||
|
if (this.newChecklistItem) this.addChecklistItem();
|
||||||
|
|
||||||
if (this.purpose === 'create') {
|
if (this.purpose === 'create') {
|
||||||
if (this.challengeId) {
|
if (this.challengeId) {
|
||||||
this.$store.dispatch('tasks:createChallengeTasks', {
|
this.$store.dispatch('tasks:createChallengeTasks', {
|
||||||
|
|||||||
@@ -12,7 +12,11 @@
|
|||||||
.input-group
|
.input-group
|
||||||
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
||||||
.filter-panel(v-if="isFilterPanelOpen")
|
.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
|
.tags-header
|
||||||
strong(v-once) {{ $t(tagsType.key) }}
|
strong(v-once) {{ $t(tagsType.key) }}
|
||||||
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
|
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
|
||||||
@@ -21,7 +25,7 @@
|
|||||||
template(v-if="editingTags && tagsType.key === 'tags'")
|
template(v-if="editingTags && tagsType.key === 'tags'")
|
||||||
.col-6(v-for="(tag, tagIndex) in tagsSnap")
|
.col-6(v-for="(tag, tagIndex) in tagsSnap")
|
||||||
.inline-edit-input-group.tag-edit-item.input-group
|
.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)")
|
span.input-group-btn(@click="removeTag(tagIndex)")
|
||||||
.svg-icon.destroy-icon(v-html="icons.destroy")
|
.svg-icon.destroy-icon(v-html="icons.destroy")
|
||||||
.col-6
|
.col-6
|
||||||
@@ -377,6 +381,7 @@ export default {
|
|||||||
this.tagsSnap.splice(index, 1);
|
this.tagsSnap.splice(index, 1);
|
||||||
},
|
},
|
||||||
saveTags () {
|
saveTags () {
|
||||||
|
if (this.newTag) this.addTag();
|
||||||
this.setUser({tags: this.tagsSnap});
|
this.setUser({tags: this.tagsSnap});
|
||||||
this.cancelTagsEditing();
|
this.cancelTagsEditing();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,23 +6,8 @@ export default {
|
|||||||
...mapState({notifications: 'notificationStore'}),
|
...mapState({notifications: 'notificationStore'}),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
|
||||||
Show '+ 5 {gold_coin} 3 {silver_coin}'
|
|
||||||
*/
|
|
||||||
coins (money) {
|
coins (money) {
|
||||||
let absolute;
|
return this.round(money, 2);
|
||||||
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>`;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
crit (val) {
|
crit (val) {
|
||||||
let message = `${this.$t('critBonus')} ${Math.round(val)} %`;
|
let message = `${this.$t('critBonus')} ${Math.round(val)} %`;
|
||||||
@@ -105,8 +90,8 @@ export default {
|
|||||||
}
|
}
|
||||||
return sign;
|
return sign;
|
||||||
},
|
},
|
||||||
round (number) {
|
round (number, nDigits) {
|
||||||
return Math.abs(number.toFixed(1));
|
return Math.abs(number.toFixed(nDigits || 1));
|
||||||
},
|
},
|
||||||
notify (html, type, icon, sign) {
|
notify (html, type, icon, sign) {
|
||||||
this.notifications.push({
|
this.notifications.push({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import axios from 'axios';
|
|||||||
import compact from 'lodash/compact';
|
import compact from 'lodash/compact';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
|
|
||||||
export function fetchUserTasks (store, forceLoad = false) {
|
export function fetchUserTasks (store, options = {}) {
|
||||||
return loadAsyncResource({
|
return loadAsyncResource({
|
||||||
store,
|
store,
|
||||||
path: 'tasks',
|
path: 'tasks',
|
||||||
@@ -15,7 +15,7 @@ export function fetchUserTasks (store, forceLoad = false) {
|
|||||||
return store.dispatch('tasks:order', [response.data.data, userResource.data.tasksOrder]);
|
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;
|
const user = store.state.user.data;
|
||||||
|
|
||||||
for (let key in changes) {
|
for (let key in changes) {
|
||||||
@@ -30,6 +30,22 @@ export function set (store, changes) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
user.tags = changes[key].concat(oldTags);
|
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 {
|
} else {
|
||||||
setProps(user, key, changes[key]);
|
setProps(user, key, changes[key]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function getTaskClasses (store) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'content':
|
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';
|
return 'task-daily-todo-content-disabled';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -25,8 +25,13 @@ module.exports = function taskDefaults (task = {}) {
|
|||||||
challenge: {
|
challenge: {
|
||||||
shortName: 'None',
|
shortName: 'None',
|
||||||
},
|
},
|
||||||
group: {},
|
group: {
|
||||||
yesterDaily: true,
|
approval: {
|
||||||
|
required: false,
|
||||||
|
approved: false,
|
||||||
|
requested: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
reminders: [],
|
reminders: [],
|
||||||
attribute: 'str',
|
attribute: 'str',
|
||||||
createdAt: new Date(), // TODO these are going to be overwritten by the server...
|
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(),
|
startDate: moment().startOf('day').toDate(),
|
||||||
everyX: 1,
|
everyX: 1,
|
||||||
frequency: 'weekly',
|
frequency: 'weekly',
|
||||||
|
daysOfMonth: [],
|
||||||
|
weeksOfMonth: [],
|
||||||
|
yesterDaily: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -285,6 +285,8 @@ api.updateUser = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
let user = res.locals.user;
|
||||||
|
|
||||||
|
let promisesForTagsRemoval = [];
|
||||||
|
|
||||||
_.each(req.body, (val, key) => {
|
_.each(req.body, (val, key) => {
|
||||||
let purchasable = requiresPurchase[key];
|
let purchasable = requiresPurchase[key];
|
||||||
|
|
||||||
@@ -321,20 +323,26 @@ api.updateUser = {
|
|||||||
user.tags.push(Tag.sanitize(t));
|
user.tags.push(Tag.sanitize(t));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove from all the tasks TODO test
|
// Remove from all the tasks
|
||||||
Tasks.Task.update({
|
// NOTE each tag to remove requires a query
|
||||||
userId: user._id,
|
|
||||||
}, {
|
promisesForTagsRemoval = removedTagsIds.map(tagId => {
|
||||||
$pull: {
|
return Tasks.Task.update({
|
||||||
tags: {$in: [removedTagsIds]},
|
userId: user._id,
|
||||||
},
|
}, {
|
||||||
}, {multi: true}).exec();
|
$pull: {
|
||||||
|
tags: tagId,
|
||||||
|
},
|
||||||
|
}, {multi: true}).exec();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new NotAuthorized(res.t('messageUserOperationProtected', { operation: key }));
|
throw new NotAuthorized(res.t('messageUserOperationProtected', { operation: key }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.save();
|
|
||||||
|
await Promise.all([user.save()].concat(promisesForTagsRemoval));
|
||||||
|
|
||||||
return res.respond(200, user);
|
return res.respond(200, user);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user