Merge branch 'vue3' into feature/vue3-async-await
# Conflicts: # src/i18n/index.js # src/store/modules/labels.js # src/store/modules/tasks.js # src/views/list/views/Kanban.vue # src/views/tasks/ShowTasks.vue # src/views/tasks/TaskDetailView.vue
This commit is contained in:
40
src/helpers/labels.test.ts
Normal file
40
src/helpers/labels.test.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {filterLabelsByQuery} from './labels'
|
||||
|
||||
describe('filter labels', () => {
|
||||
const state = {
|
||||
labels: [
|
||||
{id: 1, title: 'label1'},
|
||||
{id: 2, title: 'label2'},
|
||||
{id: 3, title: 'label3'},
|
||||
{id: 4, title: 'label4'},
|
||||
{id: 5, title: 'label5'},
|
||||
{id: 6, title: 'label6'},
|
||||
{id: 7, title: 'label7'},
|
||||
{id: 8, title: 'label8'},
|
||||
{id: 9, title: 'label9'},
|
||||
],
|
||||
}
|
||||
|
||||
it('should return an empty array for an empty query', () => {
|
||||
const labels = filterLabelsByQuery(state, [], '')
|
||||
|
||||
expect(labels).toHaveLength(0)
|
||||
})
|
||||
it('should return labels for a query', () => {
|
||||
const labels = filterLabelsByQuery(state, [], 'label2')
|
||||
|
||||
expect(labels).toHaveLength(1)
|
||||
expect(labels[0].title).toBe('label2')
|
||||
})
|
||||
it('should not return found but hidden labels', () => {
|
||||
interface label {
|
||||
id: number,
|
||||
title: string,
|
||||
}
|
||||
|
||||
const labelsToHide: label[] = [{id: 1, title: 'label1'}]
|
||||
const labels = filterLabelsByQuery(state, labelsToHide, 'label1')
|
||||
|
||||
expect(labels).toHaveLength(0)
|
||||
})
|
||||
})
|
40
src/helpers/labels.ts
Normal file
40
src/helpers/labels.ts
Normal file
@ -0,0 +1,40 @@
|
||||
interface label {
|
||||
id: number,
|
||||
title: string,
|
||||
}
|
||||
|
||||
interface labelState {
|
||||
labels: label[],
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a list of labels is available in the store and filters them then query
|
||||
* @param {Object} state
|
||||
* @param {Array} labelsToHide
|
||||
* @param {String} query
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function filterLabelsByQuery(state: labelState, labelsToHide: label[], query: string) {
|
||||
if (query === '') {
|
||||
return []
|
||||
}
|
||||
|
||||
const labelQuery = query.toLowerCase()
|
||||
const labelIds = labelsToHide.map(({id}) => id)
|
||||
return Object
|
||||
.values(state.labels)
|
||||
.filter(({id, title}) => {
|
||||
return !labelIds.includes(id) && title.toLowerCase().includes(labelQuery)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the labels by id if found
|
||||
* @param {Object} state
|
||||
* @param {Array} ids
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function getLabelsByIds(state: labelState, ids: number[]) {
|
||||
return Object.values(state.labels).filter(({id}) => ids.includes(id))
|
||||
}
|
@ -21,7 +21,7 @@ export const availableLanguages = {
|
||||
const loadedLanguages = ['en'] // our default language that is preloaded
|
||||
|
||||
const setI18nLanguage = lang => {
|
||||
i18n.locale = lang
|
||||
i18n.global.locale = lang
|
||||
document.querySelector('html').setAttribute('lang', lang)
|
||||
return lang
|
||||
}
|
||||
@ -29,7 +29,7 @@ const setI18nLanguage = lang => {
|
||||
export const loadLanguageAsync = lang => {
|
||||
if (
|
||||
// If the same language
|
||||
i18n.locale === lang ||
|
||||
i18n.global.locale === lang ||
|
||||
// If the language was already loaded
|
||||
loadedLanguages.includes(lang)
|
||||
) {
|
||||
@ -39,7 +39,7 @@ export const loadLanguageAsync = lang => {
|
||||
// If the language hasn't been loaded yet
|
||||
return import(`./lang/${lang}.json`).then(
|
||||
messages => {
|
||||
i18n.setLocaleMessage(lang, messages.default)
|
||||
i18n.global.setLocaleMessage(lang, messages.default)
|
||||
loadedLanguages.push(lang)
|
||||
return setI18nLanguage(lang)
|
||||
},
|
||||
|
@ -63,6 +63,12 @@ export const store = createStore({
|
||||
state.online = !!import.meta.env.VITE_IS_ONLINE || online
|
||||
},
|
||||
[CURRENT_LIST](state, currentList) {
|
||||
// Server updates don't return the right. Therefore the right is reset after updating the list which is
|
||||
// confusing because all the buttons will disappear in that case. To prevent this, we're keeping the right
|
||||
// when updating the list in global state.
|
||||
if (typeof state.currentList.maxRight !== 'undefined' && (typeof currentList.maxRight === 'undefined' || currentList.maxRight === null)) {
|
||||
currentList.maxRight = state.currentList.maxRight
|
||||
}
|
||||
state.currentList = currentList
|
||||
},
|
||||
[HAS_TASKS](state, hasTasks) {
|
||||
@ -135,12 +141,6 @@ export const store = createStore({
|
||||
commit(BACKGROUND, null)
|
||||
}
|
||||
|
||||
// Server updates don't return the right. Therefore the right is reset after updating the list which is
|
||||
// confusing because all the buttons will disappear in that case. To prevent this, we're keeping the right
|
||||
// when updating the list in global state.
|
||||
if (typeof state.currentList.maxRight !== 'undefined' && (typeof currentList.maxRight === 'undefined' || currentList.maxRight === null)) {
|
||||
currentList.maxRight = state.currentList.maxRight
|
||||
}
|
||||
commit(CURRENT_LIST, currentList)
|
||||
},
|
||||
},
|
||||
|
@ -2,37 +2,10 @@ import LabelService from '@/services/label'
|
||||
import {setLoading} from '@/store/helper'
|
||||
import { success } from '@/message'
|
||||
import {i18n} from '@/i18n'
|
||||
|
||||
/**
|
||||
* Returns the labels by id if found
|
||||
* @param {Object} state
|
||||
* @param {Array} ids
|
||||
* @returns {Array}
|
||||
*/
|
||||
function getLabelsByIds(state, ids) {
|
||||
return Object.values(state.labels).filter(({id}) => ids.includes(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a list of labels is available in the store and filters them then query
|
||||
* @param {Object} state
|
||||
* @param {Array} labels
|
||||
* @param {String} query
|
||||
* @returns {Array}
|
||||
*/
|
||||
function filterLabelsByQuery(state, labels, query) {
|
||||
const labelIds = labels.map(({id}) => id)
|
||||
const foundLabels = getLabelsByIds(state, labelIds)
|
||||
const labelQuery = query.toLowerCase()
|
||||
|
||||
return foundLabels.filter(({title}) => {
|
||||
return !title.toLowerCase().includes(labelQuery)
|
||||
})
|
||||
}
|
||||
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
|
||||
|
||||
const labelService = new LabelService()
|
||||
|
||||
const getAllLabels = async (page = 1) => {
|
||||
async function getAllLabels(page = 1) {
|
||||
const labels = await labelService.getAll({}, {}, page)
|
||||
if (page < labelService.totalPages) {
|
||||
const nextLabels = await getAllLabels(page + 1)
|
||||
@ -70,7 +43,7 @@ export default {
|
||||
return (ids) => getLabelsByIds(state, ids)
|
||||
},
|
||||
filterLabelsByQuery(state) {
|
||||
return (...arr) => filterLabelsByQuery(state, ...arr)
|
||||
return (labelsToHide, query) => filterLabelsByQuery(state, labelsToHide, query)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
|
@ -229,8 +229,8 @@ export default {
|
||||
|
||||
// label not found, create it
|
||||
const labelModel = new LabelModel({title: labelTitle})
|
||||
await dispatch('labels/createLabel', labelModel)
|
||||
addLabelToTask(task, label)
|
||||
await dispatch('labels/createLabel', labelModel, {root: true})
|
||||
return addLabelToTask(task, label)
|
||||
})
|
||||
|
||||
// This waits until all labels are created and added to the task
|
||||
@ -238,18 +238,18 @@ export default {
|
||||
return task
|
||||
},
|
||||
|
||||
findListId({ rootGetters }, { list, listId }) {
|
||||
findListId({ rootGetters }, { list: listName, listId }) {
|
||||
let foundListId = null
|
||||
|
||||
// Uses the following ways to get the list id of the new task:
|
||||
// 1. If specified in quick add magic, look in store if it exists and use it if it does
|
||||
if (list !== null) {
|
||||
const list = rootGetters['lists/findListByExactname'](list)
|
||||
if (listName !== null) {
|
||||
const list = rootGetters['lists/findListByExactname'](listName)
|
||||
foundListId = list === null ? null : list.id
|
||||
}
|
||||
|
||||
// 2. Else check if a list was passed as parameter
|
||||
if (listId !== 0) {
|
||||
if (foundListId === null && listId !== 0) {
|
||||
foundListId = listId
|
||||
}
|
||||
|
||||
@ -298,7 +298,7 @@ export default {
|
||||
const createdTask = await taskService.create(task)
|
||||
return dispatch('addLabelsToTask', {
|
||||
task: createdTask,
|
||||
parsedLabels:parsedTask.labels,
|
||||
parsedLabels: parsedTask.labels,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
@ -72,6 +72,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
||||
margin: .5rem;
|
||||
border-radius: $radius;
|
||||
background: $task-background;
|
||||
padding-bottom: .125rem;
|
||||
|
||||
&.loader-container.is-loading:after {
|
||||
width: 1.5rem;
|
||||
@ -122,6 +123,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
||||
.tag, .assignees, .icon, .priority-label, .checklist-summary {
|
||||
margin-top: 0;
|
||||
margin-right: .25rem;
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
.checklist-summary {
|
||||
|
@ -15,8 +15,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.has-task-edit-open {
|
||||
flex-direction: column;
|
||||
|
||||
@media screen and (min-width: $tablet) {
|
||||
flex-direction: row;
|
||||
|
||||
.tasks {
|
||||
width: 66%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.taskedit {
|
||||
width: 50%;
|
||||
width: 33%;
|
||||
margin-right: 1rem;
|
||||
margin-left: .5rem;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,16 +48,6 @@
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
@media screen and (min-width: $tablet) {
|
||||
&.short {
|
||||
max-width: 53vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.noborder {
|
||||
margin: 1rem -0.5rem;
|
||||
}
|
||||
@ -195,7 +208,7 @@
|
||||
border-bottom-color: $grey-300;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.checklist-summary {
|
||||
padding-left: .5rem;
|
||||
font-size: .9rem;
|
||||
@ -235,9 +248,6 @@
|
||||
}
|
||||
|
||||
.taskedit {
|
||||
min-height: calc(100% - 1rem);
|
||||
margin-top: 1rem;
|
||||
|
||||
.priority-select {
|
||||
.select, select {
|
||||
width: 100%;
|
||||
|
@ -405,7 +405,7 @@ export default {
|
||||
tasks,
|
||||
}
|
||||
|
||||
this.$store.dispatch('kanban/updateBucket', newBucket)
|
||||
this.$store.commit('kanban/setBucketById', newBucket)
|
||||
},
|
||||
|
||||
async updateTaskPosition(e) {
|
||||
|
@ -74,9 +74,8 @@
|
||||
</a>
|
||||
</nothing>
|
||||
|
||||
<div class="tasks-container">
|
||||
<div class="tasks-container" :class="{ 'has-task-edit-open': isTaskEdit }">
|
||||
<div
|
||||
:class="{ short: isTaskEdit }"
|
||||
class="tasks mt-0"
|
||||
v-if="tasks && tasks.length > 0"
|
||||
>
|
||||
|
@ -207,9 +207,7 @@ export default {
|
||||
// soonest before the later ones.
|
||||
// We can't use the api sorting here because that sorts tasks with a due date after
|
||||
// ones without a due date.
|
||||
this.tasks = tasks.sort((a, b) => {
|
||||
return Number(b.dueDate === null) - Number(a.dueDate === null)
|
||||
})
|
||||
this.tasks = tasks.sort((a, b) => a.dueDate - b.dueDate)
|
||||
},
|
||||
|
||||
// FIXME: this modification should happen in the store
|
||||
|
@ -510,7 +510,10 @@ export default {
|
||||
},
|
||||
parent: {
|
||||
handler(parent) {
|
||||
this.$store.dispatch(CURRENT_LIST, parent !== null ? parent.list : this.currentList)
|
||||
const parentList = parent !== null ? parent.list : null
|
||||
if (parentList !== null) {
|
||||
this.$store.commit(CURRENT_LIST, parentList)
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
|
Reference in New Issue
Block a user