1
0

Merge branch 'main' into fix/upcoming

# Conflicts:
#	src/views/tasks/ShowTasks.vue
This commit is contained in:
kolaente
2022-01-09 11:48:37 +01:00
96 changed files with 1867 additions and 2578 deletions

View File

@ -21,7 +21,7 @@
</div>
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
<x-button
type="secondary"
variant="secondary"
@click.prevent.stop="$router.back()"
>
{{ $t('misc.close') }}

View File

@ -1,7 +1,7 @@
<template>
<div class="content has-text-centered">
<h2 v-if="userInfo">
{{ $t(`home.welcome${welcome}`, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
{{ $t(welcome, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
</h2>
<message variant="danger" v-if="deletionScheduledAt !== null" class="mb-4">
{{
@ -57,7 +57,6 @@
<script lang="ts" setup>
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import {useNow} from '@vueuse/core'
import Message from '@/components/misc/message.vue'
import ShowTasks from '@/views/tasks/ShowTasks.vue'
@ -67,36 +66,15 @@ import AddTask from '@/components/tasks/add-task.vue'
import {getHistory} from '@/modules/listHistory'
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
import {useDateTimeSalutation} from '@/composables/useDateTimeSalutation'
const now = useNow()
const welcome = computed(() => {
const hours = new Date(now.value).getHours()
if (hours < 5) {
return 'Night'
}
if (hours < 11) {
return 'Morning'
}
if (hours < 18) {
return 'Day'
}
if (hours < 23) {
return 'Evening'
}
return 'Night'
})
const welcome = useDateTimeSalutation()
const store = useStore()
const listHistory = computed(() => {
const history = getHistory()
return history.map(l => {
return store.getters['lists/getListById'](l.id)
}).filter(l => l !== null)
return getHistory()
.map(l => store.getters['lists/getListById'](l.id))
.filter(l => l !== null)
})

View File

@ -4,8 +4,8 @@
primary-icon=""
:primary-label="$t('misc.save')"
@primary="saveSavedFilter"
:tertary="$t('misc.delete')"
@tertary="$router.push({ name: 'filter.settings.delete', params: { id: $route.params.listId } })"
:tertiary="$t('misc.delete')"
@tertiary="$router.push({ name: 'filter.settings.delete', params: { id: $route.params.listId } })"
>
<form @submit.prevent="saveSavedFilter()">
<div class="field">

View File

@ -6,8 +6,8 @@
class="list-background-setting"
:wide="true"
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
:tertary="hasBackground ? $t('list.background.remove') : ''"
@tertary="removeBackground()"
:tertiary="hasBackground ? $t('list.background.remove') : ''"
@tertiary="removeBackground()"
>
<div class="mb-4" v-if="uploadBackgroundEnabled">
<input
@ -20,7 +20,7 @@
<x-button
:loading="backgroundUploadService.loading"
@click="$refs.backgroundUploadInput.click()"
type="primary"
variant="primary"
>
{{ $t('list.background.upload') }}
</x-button>
@ -54,7 +54,7 @@
@click="() => searchBackgrounds(currentPage + 1)"
class="is-load-more-button mt-4"
:shadow="false"
type="secondary"
variant="secondary"
v-if="backgroundSearchResult.length > 0"
>
{{ backgroundService.loading ? $t('misc.loading') : $t('list.background.loadMore') }}

View File

@ -4,8 +4,8 @@
primary-icon=""
:primary-label="$t('misc.save')"
@primary="save"
:tertary="$t('misc.delete')"
@tertary="$router.push({ name: 'list.list.settings.delete', params: { id: $route.params.listId } })"
:tertiary="$t('misc.delete')"
@tertiary="$router.push({ name: 'list.list.settings.delete', params: { id: $route.params.listId } })"
>
<div class="field">
<label class="label" for="title">{{ $t('list.title') }}</label>

View File

@ -79,6 +79,7 @@
:disabled="bucket.limit < 0"
:icon="['far', 'save']"
:shadow="false"
v-cy="'setBucketLimit'"
/>
</div>
</div>
@ -165,7 +166,7 @@
:shadow="false"
v-if="!showNewTaskInput[bucket.id]"
icon="plus"
type="secondary"
variant="secondary"
>
{{
bucket.tasks.length === 0 ? $t('list.kanban.addTask') : $t('list.kanban.addAnotherTask')
@ -195,7 +196,7 @@
:shadow="false"
class="is-transparent is-fullwidth has-text-centered"
v-else
type="secondary"
variant="secondary"
icon="plus"
>
{{ $t('list.kanban.addBucket') }}
@ -701,7 +702,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
height: $bucket-header-height;
.limit {
padding-left: .5rem;
padding: 0 .5rem;
font-weight: bold;
&.is-max {

View File

@ -37,7 +37,7 @@
<x-button
@click="showTaskSearch = !showTaskSearch"
icon="search"
type="secondary"
variant="secondary"
v-if="!showTaskSearch"
/>
</div>

View File

@ -7,7 +7,7 @@
<x-button
@click.prevent.stop="toggle()"
icon="th"
type="secondary"
variant="secondary"
>
{{ $t('list.table.columns') }}
</x-button>

View File

@ -53,7 +53,7 @@
</p>
<div class="buttons">
<x-button @click="migrate">{{ $t('migrate.confirm') }}</x-button>
<x-button :to="{name: 'home'}" type="tertary" class="has-text-danger">{{ $t('misc.cancel') }}</x-button>
<x-button :to="{name: 'home'}" variant="tertiary" class="has-text-danger">{{ $t('misc.cancel') }}</x-button>
</div>
</div>
<div v-else>

View File

@ -24,17 +24,17 @@ export const MIGRATORS: IMigratorRecord = {
todoist: {
id: 'todoist',
name: 'Todoist',
icon: todoistIcon,
icon: todoistIcon as string,
},
trello: {
id: 'trello',
name: 'Trello',
icon: trelloIcon,
icon: trelloIcon as string,
},
'microsoft-todo': {
id: 'microsoft-todo',
name: 'Microsoft Todo',
icon: microsoftTodoIcon,
icon: microsoftTodoIcon as string,
},
'vikunja-file': {
id: 'vikunja-file',

View File

@ -1,15 +1,19 @@
<template>
<div class="content namespaces-list loader-container" :class="{'is-loading': loading}">
<x-button :to="{name: 'namespace.create'}" class="new-namespace" icon="plus">
{{ $t('namespace.create.title') }}
</x-button>
<x-button :to="{name: 'filters.create'}" class="new-namespace" icon="filter">
{{ $t('filters.create.title') }}
</x-button>
<div class="content loader-container" :class="{'is-loading': loading}" v-cy="'namespaces-list'">
<header class="namespace-header">
<fancycheckbox v-model="showArchived" @change="saveShowArchivedState" v-cy="'show-archived-check'">
{{ $t('namespace.showArchived') }}
</fancycheckbox>
<fancycheckbox class="show-archived-check" v-model="showArchived" @change="saveShowArchivedState">
{{ $t('namespace.showArchived') }}
</fancycheckbox>
<div class="action-buttons">
<x-button :to="{name: 'filters.create'}" icon="filter">
{{ $t('filters.create.title') }}
</x-button>
<x-button :to="{name: 'namespace.create'}" icon="plus" v-cy="'new-namespace'">
{{ $t('namespace.create.title') }}
</x-button>
</div>
</header>
<p class="has-text-centered has-text-grey mt-4 is-italic" v-if="namespaces.length === 0">
{{ $t('namespace.noneAvailable') }}
@ -22,7 +26,7 @@
<x-button
:to="{name: 'list.create', params: {id: n.id}}"
class="is-pulled-right"
type="secondary"
variant="secondary"
v-if="n.id > 0 && n.lists.length > 0"
icon="plus"
>
@ -31,19 +35,19 @@
<x-button
:to="{name: 'namespace.settings.archive', params: {id: n.id}}"
class="is-pulled-right mr-4"
type="secondary"
variant="secondary"
v-if="n.isArchived"
icon="archive"
>
{{ $t('namespace.unarchive') }}
</x-button>
<h1>
<span>{{ getNamespaceTitle(n) }}</span>
<h2 class="namespace-title">
<span v-cy="'namespace-title'">{{ getNamespaceTitle(n) }}</span>
<span class="is-archived" v-if="n.isArchived">
{{ $t('namespace.archived') }}
</span>
</h1>
</h2>
<p class="has-text-centered has-text-grey mt-4 is-italic" v-if="n.lists.length === 0">
{{ $t('namespace.noLists') }}
@ -103,47 +107,53 @@ export default {
</script>
<style lang="scss" scoped>
.namespaces-list {
.button.new-namespace {
float: right;
margin-left: 1rem;
.namespace-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
@media screen and (max-width: $mobile) {
float: none;
width: 100%;
margin-bottom: 1rem;
}
}
@media screen and (max-width: $tablet) {
flex-direction: column;
}
}
.show-archived-check {
margin-bottom: 1rem;
}
.action-buttons {
display: flex;
justify-content: space-between;
gap: 1rem;
.namespace {
&:not(:last-child) {
margin-bottom: 1rem;
}
@media screen and (max-width: $tablet) {
width: 100%;
flex-direction: column;
align-items: stretch;
}
}
h1 {
display: flex;
align-items: center;
}
.namespace {
& + & {
margin-top: 1rem;
}
}
.is-archived {
font-size: 0.75rem;
border: 1px solid var(--grey-500);
color: $grey !important;
padding: 2px 4px;
border-radius: 3px;
font-family: $vikunja-font;
background: var(--white-translucent);
margin-left: .5rem;
}
.namespace-title {
display: flex;
align-items: center;
}
.lists {
display: flex;
flex-flow: row wrap;
}
}
.is-archived {
font-size: 0.75rem;
border: 1px solid var(--grey-500);
color: $grey !important;
padding: 2px 4px;
border-radius: 3px;
font-family: $vikunja-font;
background: var(--white-translucent);
margin-left: .5rem;
}
.lists {
display: flex;
flex-flow: row wrap;
}
</style>

View File

@ -4,8 +4,8 @@
primary-icon=""
:primary-label="$t('misc.save')"
@primary="save"
:tertary="$t('misc.delete')"
@tertary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id } })"
:tertiary="$t('misc.delete')"
@tertiary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id } })"
>
<form @submit.prevent="save()">
<div class="field">

View File

@ -9,7 +9,7 @@
<script lang="ts" setup>
import { ref } from 'vue'
import ShowTasks from './ShowTasks'
import ShowTasks from './ShowTasks.vue'
function getNextWeekDate() {
return new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000)

View File

@ -258,7 +258,7 @@
@click="toggleTaskDone()"
class="is-outlined has-no-border"
icon="check-double"
type="secondary"
variant="secondary"
>
{{ task.done ? $t('task.detail.undone') : $t('task.detail.done') }}
</x-button>
@ -270,7 +270,7 @@
/>
<x-button
@click="setFieldActive('assignees')"
type="secondary"
variant="secondary"
v-shortcut="'a'"
v-cy="'taskDetail.assign'"
>
@ -279,7 +279,7 @@
</x-button>
<x-button
@click="setFieldActive('labels')"
type="secondary"
variant="secondary"
icon="tags"
v-shortcut="'l'"
>
@ -287,14 +287,14 @@
</x-button>
<x-button
@click="setFieldActive('priority')"
type="secondary"
variant="secondary"
icon="exclamation"
>
{{ $t('task.detail.actions.priority') }}
</x-button>
<x-button
@click="setFieldActive('dueDate')"
type="secondary"
variant="secondary"
icon="calendar"
v-shortcut="'d'"
>
@ -302,42 +302,42 @@
</x-button>
<x-button
@click="setFieldActive('startDate')"
type="secondary"
variant="secondary"
icon="play"
>
{{ $t('task.detail.actions.startDate') }}
</x-button>
<x-button
@click="setFieldActive('endDate')"
type="secondary"
variant="secondary"
icon="stop"
>
{{ $t('task.detail.actions.endDate') }}
</x-button>
<x-button
@click="setFieldActive('reminders')"
type="secondary"
variant="secondary"
:icon="['far', 'clock']"
>
{{ $t('task.detail.actions.reminders') }}
</x-button>
<x-button
@click="setFieldActive('repeatAfter')"
type="secondary"
variant="secondary"
icon="history"
>
{{ $t('task.detail.actions.repeatAfter') }}
</x-button>
<x-button
@click="setFieldActive('percentDone')"
type="secondary"
variant="secondary"
icon="percent"
>
{{ $t('task.detail.actions.percentDone') }}
</x-button>
<x-button
@click="setFieldActive('attachments')"
type="secondary"
variant="secondary"
icon="paperclip"
v-shortcut="'f'"
>
@ -345,7 +345,7 @@
</x-button>
<x-button
@click="setFieldActive('relatedTasks')"
type="secondary"
variant="secondary"
icon="sitemap"
v-shortcut="'r'"
>
@ -353,21 +353,21 @@
</x-button>
<x-button
@click="setFieldActive('moveList')"
type="secondary"
variant="secondary"
icon="list"
>
{{ $t('task.detail.actions.moveList') }}
</x-button>
<x-button
@click="setFieldActive('color')"
type="secondary"
variant="secondary"
icon="fill-drip"
>
{{ $t('task.detail.actions.color') }}
</x-button>
<x-button
@click="toggleFavorite"
type="secondary"
variant="secondary"
:icon="task.isFavorite ? 'star' : ['far', 'star']"
>
{{
@ -627,7 +627,6 @@ export default {
}
this.task = await this.$store.dispatch('tasks/update', this.task)
this.setActiveFields()
if (!showNotification) {
return
@ -871,7 +870,7 @@ $flash-background-duration: 750ms;
}
.action-buttons {
a.button {
.button {
width: 100%;
margin-bottom: .5rem;
justify-content: left;

View File

@ -67,7 +67,7 @@
<x-button
:to="{ name: 'user.register' }"
v-if="registrationEnabled"
type="secondary"
variant="secondary"
>
{{ $t('user.auth.register') }}
</x-button>
@ -87,7 +87,7 @@
@click="redirectToProvider(p)"
v-for="(p, k) in openidConnect.providers"
:key="k"
type="secondary"
variant="secondary"
class="is-fullwidth mt-2"
>
{{ $t('user.auth.loginWith', {provider: p.name}) }}

View File

@ -79,7 +79,7 @@
>
{{ $t('user.auth.register') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">
<x-button :to="{ name: 'user.login' }" variant="secondary">
{{ $t('user.auth.login') }}
</x-button>
</div>

View File

@ -35,7 +35,7 @@
>
{{ $t('user.auth.resetPasswordAction') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">
<x-button :to="{ name: 'user.login' }" variant="secondary">
{{ $t('user.auth.login') }}
</x-button>
</div>

View File

@ -48,6 +48,7 @@
<x-button
:loading="avatarService.loading || loading"
@click="uploadAvatar"
v-cy="'uploadAvatar'"
>
{{ $t('user.settings.avatar.uploadAvatar') }}
</x-button>

View File

@ -1,5 +1,5 @@
<template>
<card :title="$t('user.settings.general.title')" class="general-settings" :loading="userSettingsService.loading">
<card :title="$t('user.settings.general.title')" class="general-settings" :loading="loading">
<div class="field">
<label class="label" :for="`newName${id}`">{{ $t('user.settings.general.name') }}</label>
<div class="control">
@ -67,8 +67,8 @@
{{ $t('user.settings.general.language') }}
</span>
<div class="select ml-2">
<select v-model="language">
<option :value="lang.code" v-for="lang in availableLanguages" :key="lang.code">{{
<select v-model="settings.language">
<option :value="lang.code" v-for="lang in availableLanguageOptions" :key="lang.code">{{
lang.title
}}
</option>
@ -107,9 +107,10 @@
</div>
<x-button
:loading="userSettingsService.loading"
:loading="loading"
@click="updateSettings()"
class="is-fullwidth mt-4"
v-cy="'saveGeneralSettings'"
>
{{ $t('misc.save') }}
</x-button>
@ -120,14 +121,12 @@
import {computed, watch} from 'vue'
import {useI18n} from 'vue-i18n'
import {playSoundWhenDoneKey} from '@/helpers/playPop'
import {availableLanguages, saveLanguage, getCurrentLanguage} from '@/i18n'
import {playSoundWhenDoneKey, playPop} from '@/helpers/playPop'
import {availableLanguages} from '@/i18n'
import {getQuickAddMagicMode, setQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
import UserSettingsService from '@/services/userSettings'
import {PrefixMode} from '@/modules/parseTaskText'
import ListSearch from '@/components/tasks/partials/listSearch'
import {createRandomID} from '@/helpers/randomId'
import {playPop} from '@/helpers/playPop'
import {useColorScheme} from '@/composables/useColorScheme'
import {success} from '@/message'
@ -165,23 +164,19 @@ export default {
data() {
return {
playSoundWhenDone: getPlaySoundWhenDoneSetting(),
language: getCurrentLanguage(),
quickAddMagicMode: getQuickAddMagicMode(),
quickAddMagicPrefixes: PrefixMode,
userSettingsService: new UserSettingsService(),
settings: {...this.$store.state.auth.settings},
id: createRandomID(),
availableLanguageOptions: Object.entries(availableLanguages)
.map(l => ({code: l[0], title: l[1]}))
.sort((a, b) => a.title.localeCompare(b.title)),
}
},
components: {
ListSearch,
},
computed: {
availableLanguages() {
return Object.entries(availableLanguages)
.map(l => ({code: l[0], title: l[1]}))
.sort((a, b) => a.title.localeCompare(b.title))
},
defaultList: {
get() {
return this.$store.getters['lists/getListById'](this.settings.defaultListId)
@ -190,6 +185,9 @@ export default {
this.settings.defaultListId = l ? l.id : DEFAULT_LIST_ID
},
},
loading() {
return this.$store.state.loading && this.$store.state.loadingModule === 'general-settings'
},
},
setup() {
@ -211,16 +209,11 @@ export default {
methods: {
async updateSettings() {
localStorage.setItem(playSoundWhenDoneKey, this.playSoundWhenDone)
saveLanguage(this.language)
setQuickAddMagicMode(this.quickAddMagicMode)
const settings = {
...this.settings,
}
await this.userSettingsService.update(settings)
this.$store.commit('auth/setUserSettings', settings)
this.$message.success({message: this.$t('user.settings.general.savedSuccess')})
await this.$store.dispatch('auth/saveUserSettings', {
settings: {...this.settings},
})
},
},
}

View File

@ -55,7 +55,7 @@
<x-button @click="totpDisable" class="is-danger">
{{ $t('user.settings.totp.disable') }}
</x-button>
<x-button @click="totpDisableForm = false" type="tertary" class="ml-2">
<x-button @click="totpDisableForm = false" variant="tertiary" class="ml-2">
{{ $t('misc.cancel') }}
</x-button>
</div>