Merge branch 'main' into feature/caldav-tokens

This commit is contained in:
kolaente 2021-12-26 11:30:12 +01:00
commit 9820fcba31
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
28 changed files with 546 additions and 508 deletions

View File

@ -22,8 +22,8 @@
"@sentry/tracing": "6.16.1",
"@sentry/vue": "6.16.1",
"@vue/compat": "3.2.26",
"@vueuse/core": "7.4.0",
"@vueuse/router": "7.4.0",
"@vueuse/core": "7.4.1",
"@vueuse/router": "7.4.1",
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
"codemirror": "5.65.0",
@ -46,7 +46,7 @@
"vue-advanced-cropper": "2.7.0",
"vue-drag-resize": "2.0.3",
"vue-flatpickr-component": "9.0.5",
"vue-i18n": "9.2.0-beta.23",
"vue-i18n": "9.2.0-beta.25",
"vue-router": "4.0.12",
"vuedraggable": "4.1.0",
"vuex": "4.0.2",
@ -60,36 +60,36 @@
"@fortawesome/vue-fontawesome": "3.0.0-5",
"@types/flexsearch": "0.7.2",
"@types/jest": "27.0.3",
"@typescript-eslint/eslint-plugin": "5.7.0",
"@typescript-eslint/parser": "5.7.0",
"@typescript-eslint/eslint-plugin": "5.8.0",
"@typescript-eslint/parser": "5.8.0",
"@vitejs/plugin-legacy": "1.6.4",
"@vitejs/plugin-vue": "2.0.1",
"@vue/eslint-config-typescript": "9.1.0",
"autoprefixer": "10.4.0",
"axios": "0.24.0",
"browserslist": "4.19.1",
"caniuse-lite": "1.0.30001291",
"cypress": "9.1.1",
"caniuse-lite": "1.0.30001292",
"cypress": "9.2.0",
"cypress-file-upload": "5.0.8",
"esbuild": "0.14.6",
"esbuild": "0.14.8",
"eslint": "8.5.0",
"eslint-plugin-vue": "8.2.0",
"express": "4.17.2",
"faker": "5.5.3",
"jest": "27.4.5",
"netlify-cli": "8.2.0",
"netlify-cli": "8.4.2",
"postcss": "8.4.5",
"postcss-preset-env": "7.0.2",
"postcss-preset-env": "7.1.0",
"rollup": "2.61.1",
"rollup-plugin-visualizer": "5.5.2",
"sass": "1.45.0",
"sass": "1.45.1",
"slugify": "1.6.4",
"ts-jest": "27.1.2",
"typescript": "4.5.4",
"vite": "2.7.4",
"vite": "2.7.7",
"vite-plugin-pwa": "0.11.12",
"vite-svg-loader": "3.1.1",
"vue-tsc": "0.29.8",
"vue-tsc": "0.30.0",
"wait-on": "6.0.0",
"workbox-cli": "6.4.2"
},

View File

@ -6,8 +6,10 @@
<content-auth/>
</template>
<content-link-share v-else-if="authLinkShare"/>
<content-no-auth v-else/>
<notification/>
<no-auth-wrapper v-else>
<router-view/>
</no-auth-wrapper>
<Notification/>
</div>
<transition name="fade">
@ -31,7 +33,7 @@ import KeyboardShortcuts from './components/misc/keyboard-shortcuts/index.vue'
import TopNavigation from './components/home/topNavigation.vue'
import ContentAuth from './components/home/contentAuth.vue'
import ContentLinkShare from './components/home/contentLinkShare.vue'
import ContentNoAuth from './components/home/contentNoAuth.vue'
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import Ready from '@/components/misc/ready.vue'
import {setLanguage} from './i18n'

View File

@ -64,21 +64,25 @@ const route = useRoute()
// hide menu on mobile
watch(() => route.fullPath, () => window.innerWidth < 769 && store.commit(MENU_ACTIVE, false))
// FIXME: this is really error prone
// Reset the current list highlight in menu if the current route is not list related.
watch(() => route.fullPath, () => {
watch(() => route.name as string, (routeName) => {
if (
[
'home',
'namespace.edit',
'teams.index',
'teams.edit',
'tasks.range',
'labels.index',
'migrate.start',
'migrate.wunderlist',
'namespaces.index',
].includes(route.name) ||
route.name.startsWith('user.settings')
routeName &&
(
[
'home',
'namespace.edit',
'teams.index',
'teams.edit',
'tasks.range',
'labels.index',
'migrate.start',
'migrate.wunderlist',
'namespaces.index',
].includes(routeName) ||
routeName.startsWith('user.settings')
)
) {
store.dispatch(CURRENT_LIST, null)
}

View File

@ -1,41 +0,0 @@
<template>
<no-auth-wrapper>
<router-view/>
</no-auth-wrapper>
</template>
<script lang="ts" setup>
import {watchEffect} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import NoAuthWrapper from '@/components/misc/no-auth-wrapper'
import {saveLastVisited} from '@/helpers/saveLastVisited'
const route = useRoute()
watchEffect(() => {
if (!route.name) return
redirectToHome()
})
const router = useRouter()
function redirectToHome() {
// Check if the user is already logged in and redirect them to the home page if not
if (
![
'user.login',
'user.password-reset.request',
'user.password-reset.reset',
'user.register',
'link-share.auth',
'openid.auth',
].includes(route.name) &&
localStorage.getItem('passwordResetToken') === null &&
localStorage.getItem('emailConfirmToken') === null
) {
saveLastVisited(route.name, route.params)
router.push({name: 'user.login'})
}
}
</script>

View File

@ -1,23 +1,24 @@
<template>
<card class="filters has-overflow">
<fancycheckbox v-model="params.filter_include_nulls">
{{ $t('filters.attributes.includeNulls') }}
</fancycheckbox>
<fancycheckbox
v-model="filters.requireAllFilters"
@change="setFilterConcat()"
>
{{ $t('filters.attributes.requireAll') }}
</fancycheckbox>
<div class="field">
<label class="label">
<fancycheckbox v-model="params.filter_include_nulls">
{{ $t('filters.attributes.includeNulls') }}
</fancycheckbox>
<fancycheckbox
v-model="filters.requireAllFilters"
@change="setFilterConcat()"
>
{{ $t('filters.attributes.requireAll') }}
</fancycheckbox>
<fancycheckbox @change="setDoneFilter" v-model="filters.done">
{{ $t('filters.attributes.showDoneTasks') }}
</label>
<div class="control">
<fancycheckbox @change="setDoneFilter" v-model="filters.done">
{{ $t('filters.attributes.showDoneTasks') }}
</fancycheckbox>
</div>
</fancycheckbox>
<fancycheckbox
v-if="!$route.name.includes('list.kanban') || !$route.name.includes('list.table')"
v-model="sortAlphabetically"
>
{{ $t('filters.attributes.sortAlphabetically') }}
</fancycheckbox>
</div>
<div class="field">
<label class="label">{{ $t('misc.search') }}</label>
@ -190,6 +191,7 @@ import NamespaceService from '@/services/namespace'
import EditLabels from '@/components/tasks/partials/editLabels.vue'
import {objectToSnakeCase} from '@/helpers/case'
import {getDefaultParams} from '@/components/tasks/mixins/taskList'
// FIXME: merge with DEFAULT_PARAMS in taskList.js
const DEFAULT_PARAMS = {
@ -220,6 +222,8 @@ const DEFAULT_FILTERS = {
namespace: '',
}
export const ALPHABETICAL_SORT = 'title'
export default {
name: 'filters',
components: {
@ -272,6 +276,18 @@ export default {
},
},
computed: {
sortAlphabetically: {
get() {
return this.params?.sort_by?.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined
},
set(sortAlphabetically) {
this.params.sort_by = sortAlphabetically
? [ALPHABETICAL_SORT]
: getDefaultParams().sort_by
this.change()
},
},
foundLabels() {
return this.$store.getters['labels/filterLabelsByQuery'](this.labels, this.query)
},

View File

@ -44,7 +44,6 @@ export default {
params = null,
forceLoading = false,
) {
// Because this function is triggered every time on topNavigation, we're putting a condition here to only load it when we actually want to show tasks
// FIXME: This is a bit hacky -> Cleanup.
if (

View File

@ -1,11 +0,0 @@
export const playSoundWhenDoneKey = 'playSoundWhenTaskDone'
export const playPop = () => {
const enabled = localStorage.getItem(playSoundWhenDoneKey) === 'true' || localStorage.getItem(playSoundWhenDoneKey) === null
if(!enabled) {
return
}
const popSound = new Audio('/audio/pop.mp3')
popSound.play()
}

13
src/helpers/playPop.ts Normal file
View File

@ -0,0 +1,13 @@
import popSoundFile from '@/assets/audio/pop.mp3'
export const playSoundWhenDoneKey = 'playSoundWhenTaskDone'
export function playPop() {
const enabled = Boolean(localStorage.getItem(playSoundWhenDoneKey))
if(!enabled) {
return
}
const popSound = new Audio(popSoundFile)
popSound.play()
}

View File

@ -374,6 +374,7 @@
"includeNulls": "Zahrnout úkoly, které nemají nastavenou hodnotu",
"requireAll": "Vyžaduje aby všechny filtry odpovídaly, aby se úkol zobrazil",
"showDoneTasks": "Zobrazit dokončené úkoly",
"sortAlphabetically": "Řadit podle abecedy",
"enablePriority": "Povolit filtrování podle priority",
"enablePercentDone": "Povolit filtrování dle dokončenosti",
"dueDateRange": "Rozsah termínu",

View File

@ -374,6 +374,7 @@
"includeNulls": "Aufgaben ohne Werte einbeziehen",
"requireAll": "Alle Filterkriterien müssen erfüllt sein, damit eine Aufgabe angezeigt wird",
"showDoneTasks": "Erledigte Aufgaben anzeigen",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Filter nach Priorität aktivieren",
"enablePercentDone": "Filter nach % Erledigt aktivieren",
"dueDateRange": "Fälligkeitsbereich",

View File

@ -374,6 +374,7 @@
"includeNulls": "Uufgabe ohni Wert iihbezieh",
"requireAll": "Alli Filter mend wahr sii, demits die Uufgab ahzeigt",
"showDoneTasks": "Zeig die fertige Uufgabe",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Filter nach Priorität aktiviere",
"enablePercentDone": "Filter nach Prozent iihschalte",
"dueDateRange": "Fälligkeitsberiich",

View File

@ -381,6 +381,7 @@
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",

View File

@ -374,6 +374,7 @@
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",

View File

@ -374,6 +374,7 @@
"includeNulls": "Inclure les tâches sans valeurs",
"requireAll": "Exiger tous les filtres pour quune tâche saffiche",
"showDoneTasks": "Afficher les tâches terminées",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Activer le filtre par priorité",
"enablePercentDone": "Par % dachèvement",
"dueDateRange": "Plage de dates déchéance",

View File

@ -374,6 +374,7 @@
"includeNulls": "Includi attività che non hanno un valore impostato",
"requireAll": "Tutti i filtri devono essere veri affinché l'attività venga mostrata",
"showDoneTasks": "Mostra Attività Fatte",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Abilita Filtro Per Priorità",
"enablePercentDone": "Abilitare Filtro Per Percentuale Fatta",
"dueDateRange": "Intervallo Data Di Scadenza",

View File

@ -374,6 +374,7 @@
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",

View File

@ -374,6 +374,7 @@
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",

View File

@ -374,6 +374,7 @@
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",

View File

@ -374,6 +374,7 @@
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",

View File

@ -374,6 +374,7 @@
"includeNulls": "Включать задачи, у которых не установлено значение",
"requireAll": "Для отображения задачи требовать истинность всех фильтров",
"showDoneTasks": "Показывать завершённые задачи",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Вкл. фильтр по приоритету",
"enablePercentDone": "По % завершения",
"dueDateRange": "Диапазон срока",

View File

@ -374,6 +374,7 @@
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",

View File

@ -374,6 +374,7 @@
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",

View File

@ -36,7 +36,7 @@
"password": "Mật khẩu",
"passwordRepeat": "Nhập lại mật khẩu",
"passwordPlaceholder": "ví dụ: •••••••••••",
"forgotPassword": "Forgot your password?",
"forgotPassword": "Bạn quên mật khẩu?",
"resetPassword": "Reset mật khẩu của bạn",
"resetPasswordAction": "Gửi cho tôi liên kết reset mật khẩu",
"resetPasswordSuccess": "Kiểm tra hộp thư của bạn! Bạn sẽ nhận một e-mail với hướng dẫn reset mật khẩu của mình.",
@ -103,7 +103,7 @@
"title": "Avatar",
"initials": "Chữ cái viết tắt",
"gravatar": "Gravatar",
"marble": "Marble",
"marble": "Cẩm thạch",
"upload": "Tải lên",
"uploadAvatar": "Tải lên Avatar",
"statusUpdateSuccess": "Avatar đã được cập nhật!",
@ -374,6 +374,7 @@
"includeNulls": "Bao gồm các Công việc không có bộ giá trị",
"requireAll": "Yêu cầu tất cả các bộ lọc phải đúng để một công việc được hiển thị",
"showDoneTasks": "Hiển thị các công việc đã hoàn thành",
"sortAlphabetically": "Xếp theo bảng chữ cái",
"enablePriority": "Bật Bộ lọc theo mức độ ưu tiên",
"enablePercentDone": "Bật Bộ lọc theo tỉ lệ % hoàn thành",
"dueDateRange": "Phạm vi ngày đến hạn",
@ -474,8 +475,8 @@
"download": "Tải về",
"showMenu": "Hiển thị menu",
"hideMenu": "Ẩn menu",
"forExample": "For example:",
"welcomeBack": "Welcome Back!"
"forExample": "Ví dụ:",
"welcomeBack": "Mừng quá! Bạn trở lại rồi."
},
"input": {
"resetColor": "Đặt lại màu",
@ -725,8 +726,8 @@
"dateCurrentYear": "sẽ sử dụng năm hiện tại",
"dateNth": "sẽ sử dụng ngày {day} của tháng hiện tại",
"dateTime": "Kết hợp bất kì đinh dạng ngày với \"{time}\" (hoặc {timePM}) để thiết lập thời gian.",
"repeats": "Repeating tasks",
"repeatsDescription": "To set a task as repeating in an interval, simply add '{suffix}' to the task text. The amount needs to be a number and can be omitted to use just the type (see examples)."
"repeats": "Công việc định kỳ",
"repeatsDescription": "Để cài một công việc được lặp lại trong các khoảng thời gian, chỉ cần thêm '{suffix}' vào văn bản công việc. Số lượng phải là một số và có thể được bỏ qua để chỉ sử dụng loại (xem ví dụ)."
}
},
"team": {
@ -813,7 +814,7 @@
"url": "URL Vikunja",
"urlPlaceholder": "ví dụ: https://localhost:3456",
"change": "thay đổi",
"use": "Using Vikunja installation at {0}",
"use": "Sử dụng cài đặt Vikunja tại {0}",
"error": "Không thể tìm thấy hoặc sử dụng cài đặt Vikunja tại \"{domain}\". Vui lòng thử một url khác.",
"success": "Sử dụng cài đặt Vikunja tại \"{domain}\".",
"urlRequired": "Cần có một url."
@ -907,7 +908,7 @@
"5010": "Team này không có quyền bước vào góc làm việc đó.",
"5011": "Người này đã có quyền bước vào góc làm việc đó.",
"5012": "Góc làm việc đã được lưu trữ nên chỉ có thể vào đó để đọc.",
"6001": "The team name cannot be empty.",
"6001": "Tên của Team không được để trống.",
"6002": "Team không tồn tại.",
"6004": "Team đã có quyền bước vào góc làm việc và xem danh sách.",
"6005": "Người này đã là thành viên của Team đó rồi.",

View File

@ -133,8 +133,8 @@ if (window.SENTRY_ENABLED) {
import('./sentry').then(sentry => sentry.default(app, router))
}
app.use(router)
app.use(store)
app.use(router)
app.use(i18n)
app.mount('#app')

View File

@ -1,4 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory, RouteLocation } from 'vue-router'
import {saveLastVisited} from '@/helpers/saveLastVisited'
import {store} from '@/store'
import HomeComponent from '../views/Home'
import NotFoundComponent from '../views/404'
@ -573,16 +575,34 @@ const router = createRouter({
],
})
// bad example if using named routes:
router.resolve({
name: 'bad-not-found',
params: { pathMatch: 'not/found' },
}).href // '/not%2Ffound'
router.beforeEach((to) => {
return checkAuth(to)
})
// good example:
router.resolve({
name: 'not-found',
params: { pathMatch: ['not', 'found'] },
}).href // '/not/found'
function checkAuth(route: RouteLocation) {
const authUser = store.getters['auth/authUser']
const authLinkShare = store.getters['auth/authLinkShare']
if (authUser || authLinkShare) {
return
}
// Check if the user is already logged in and redirect them to the home page if not
if (
![
'user.login',
'user.password-reset.request',
'user.password-reset.reset',
'user.register',
'link-share.auth',
'openid.auth',
].includes(route.name as string) &&
localStorage.getItem('passwordResetToken') === null &&
localStorage.getItem('emailConfirmToken') === null
) {
saveLastVisited(route.name as string, route.params)
return {name: 'user.login'}
}
}
export default router

View File

@ -81,7 +81,7 @@
:disabled="!canWrite"
item-key="id"
:component-data="{
class: { 'dragging-disabled': !canWrite },
class: { 'dragging-disabled': !canWrite || isAlphabeticalSorting },
}"
>
<template #item="{element: t}">
@ -148,6 +148,7 @@ import {HAS_TASKS} from '@/store/mutation-types'
import Nothing from '@/components/misc/nothing.vue'
import Pagination from '@/components/misc/pagination.vue'
import Popup from '@/components/misc/popup'
import { ALPHABETICAL_SORT } from '@/components/list/partials/filters'
import draggable from 'vuedraggable'
import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
@ -206,6 +207,9 @@ export default {
saveListView(this.$route.params.listId, this.$route.name)
},
computed: {
isAlphabeticalSorting() {
return this.params.sort_by.find( sortBy => sortBy === ALPHABETICAL_SORT ) !== undefined
},
firstNewPosition() {
if (this.tasks.length === 0) {
return 0
@ -254,12 +258,18 @@ export default {
focusNewTaskInput() {
this.$refs.newTaskInput.$refs.newTaskInput.focus()
},
updateTaskList(task) {
const tasks = [
task,
...this.tasks,
]
this.tasks = tasks
updateTaskList( task ) {
if ( this.isAlphabeticalSorting ) {
// reload tasks with current filter and sorting
this.loadTasks(1, undefined, undefined, true)
}
else {
this.tasks = [
task,
...this.tasks,
]
}
this.$store.commit(HAS_TASKS, true)
},
editTask(id) {

777
yarn.lock

File diff suppressed because it is too large Load Diff