forked from vikunja/frontend
Compare commits
13 Commits
0f23cc2162
...
6711a08de9
Author | SHA1 | Date |
---|---|---|
renovate | 6711a08de9 | |
renovate | 7fe33c6662 | |
renovate | e61b215dc1 | |
renovate | 3b5cb1ade3 | |
renovate | 89e28cbdf2 | |
renovate | e9e836f068 | |
kolaente | aa5e11915e | |
kolaente | 7f279c98e1 | |
kolaente | 3c1861eb6a | |
kolaente | 75262b716f | |
ThatHurleyGuy | 7e623d919e | |
kolaente | 3f42ce2b34 | |
renovate | 8b8da40265 |
38
package.json
38
package.json
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"homepage": "https://vikunja.io/",
|
||||
"funding": "https://opencollective.com/vikunja",
|
||||
"packageManager": "pnpm@8.10.2",
|
||||
"packageManager": "pnpm@8.10.5",
|
||||
"keywords": [
|
||||
"todo",
|
||||
"productivity",
|
||||
|
@ -53,8 +53,8 @@
|
|||
"@infectoone/vue-ganttastic": "2.2.0",
|
||||
"@intlify/unplugin-vue-i18n": "1.5.0",
|
||||
"@kyvg/vue3-notification": "3.0.2",
|
||||
"@sentry/tracing": "7.77.0",
|
||||
"@sentry/vue": "7.77.0",
|
||||
"@sentry/tracing": "7.80.1",
|
||||
"@sentry/vue": "7.80.1",
|
||||
"@tiptap/core": "2.1.12",
|
||||
"@tiptap/extension-blockquote": "2.1.12",
|
||||
"@tiptap/extension-bold": "2.1.12",
|
||||
|
@ -90,7 +90,7 @@
|
|||
"@tiptap/vue-3": "2.1.12",
|
||||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@types/sortablejs": "1.15.4",
|
||||
"@types/sortablejs": "1.15.5",
|
||||
"@vueuse/core": "10.5.0",
|
||||
"@vueuse/router": "10.5.0",
|
||||
"axios": "1.6.0",
|
||||
|
@ -113,11 +113,11 @@
|
|||
"snake-case": "3.0.4",
|
||||
"sortablejs": "1.15.0",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.3.1",
|
||||
"ufo": "1.3.2",
|
||||
"vue": "3.3.8",
|
||||
"vue-advanced-cropper": "2.8.8",
|
||||
"vue-flatpickr-component": "11.0.3",
|
||||
"vue-i18n": "9.6.5",
|
||||
"vue-i18n": "9.7.0",
|
||||
"vue-router": "4.2.5",
|
||||
"workbox-precaching": "7.0.0",
|
||||
"zhyswan-vuedraggable": "4.1.3"
|
||||
|
@ -126,9 +126,9 @@
|
|||
"@4tw/cypress-drag-drop": "2.2.5",
|
||||
"@cypress/vite-dev-server": "5.0.6",
|
||||
"@cypress/vue": "6.0.0",
|
||||
"@faker-js/faker": "8.2.0",
|
||||
"@faker-js/faker": "8.3.1",
|
||||
"@histoire/plugin-screenshot": "0.17.0",
|
||||
"@histoire/plugin-vue": "0.17.4",
|
||||
"@histoire/plugin-vue": "0.17.5",
|
||||
"@rushstack/eslint-patch": "1.5.1",
|
||||
"@tsconfig/node18": "18.2.2",
|
||||
"@types/codemirror": "5.60.13",
|
||||
|
@ -137,36 +137,36 @@
|
|||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.debounce": "4.0.9",
|
||||
"@types/marked": "5.0.2",
|
||||
"@types/node": "20.8.10",
|
||||
"@types/node": "20.9.1",
|
||||
"@types/postcss-preset-env": "7.7.0",
|
||||
"@types/sortablejs": "1.15.5",
|
||||
"@typescript-eslint/eslint-plugin": "6.10.0",
|
||||
"@typescript-eslint/parser": "6.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.11.0",
|
||||
"@typescript-eslint/parser": "6.11.0",
|
||||
"@vitejs/plugin-legacy": "4.1.1",
|
||||
"@vitejs/plugin-vue": "4.4.0",
|
||||
"@vitejs/plugin-vue": "4.5.0",
|
||||
"@vue/eslint-config-typescript": "12.0.0",
|
||||
"@vue/test-utils": "2.4.1",
|
||||
"@vue/test-utils": "2.4.2",
|
||||
"@vue/tsconfig": "0.4.0",
|
||||
"autoprefixer": "10.4.16",
|
||||
"browserslist": "4.22.1",
|
||||
"caniuse-lite": "1.0.30001561",
|
||||
"caniuse-lite": "1.0.30001563",
|
||||
"css-has-pseudo": "6.0.0",
|
||||
"csstype": "3.1.2",
|
||||
"cypress": "13.4.0",
|
||||
"cypress": "13.5.1",
|
||||
"esbuild": "0.19.5",
|
||||
"eslint": "8.53.0",
|
||||
"eslint-plugin-vue": "9.18.1",
|
||||
"happy-dom": "12.10.3",
|
||||
"histoire": "0.17.4",
|
||||
"histoire": "0.17.5",
|
||||
"postcss": "8.4.31",
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-focus-within": "8.0.0",
|
||||
"postcss-preset-env": "9.3.0",
|
||||
"rollup": "4.3.0",
|
||||
"rollup": "4.4.1",
|
||||
"rollup-plugin-visualizer": "5.9.2",
|
||||
"sass": "1.69.5",
|
||||
"start-server-and-test": "2.0.2",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "4.5.0",
|
||||
"vite-plugin-inject-preload": "1.3.3",
|
||||
|
@ -175,7 +175,7 @@
|
|||
"vite-svg-loader": "4.0.0",
|
||||
"vitest": "0.34.6",
|
||||
"vue-tsc": "1.8.22",
|
||||
"wait-on": "7.1.0",
|
||||
"wait-on": "7.2.0",
|
||||
"workbox-cli": "7.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
|
|
524
pnpm-lock.yaml
524
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -75,6 +75,7 @@ import {useI18n} from 'vue-i18n'
|
|||
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
|
||||
|
||||
import Popup from '@/components/misc/popup.vue'
|
||||
import {DATE_RANGES} from '@/components/date/dateRanges'
|
||||
|
@ -120,9 +121,9 @@ watch(
|
|||
to.value = newValue.dateTo
|
||||
// Only set the date back to flatpickr when it's an actual date.
|
||||
// Otherwise flatpickr runs in an endless loop and slows down the browser.
|
||||
const dateFrom = new Date(from.value)
|
||||
const dateTo = new Date(to.value)
|
||||
if (dateTo.getTime() === dateTo.getTime() && dateFrom.getTime() === dateFrom.getTime()) {
|
||||
const dateFrom = parseDateOrString(from.value, false)
|
||||
const dateTo = parseDateOrString(to.value, false)
|
||||
if (dateFrom instanceof Date && dateTo instanceof Date) {
|
||||
flatpickrRange.value = `${from.value} to ${to.value}`
|
||||
}
|
||||
},
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</router-view>
|
||||
|
||||
<modal
|
||||
:enabled="Boolean(currentModal)"
|
||||
:enabled="typeof currentModal !== 'undefined'"
|
||||
@close="closeModal()"
|
||||
variant="scrolling"
|
||||
class="task-detail-view-modal"
|
||||
|
|
|
@ -20,11 +20,20 @@ import type {IProject} from '@/modelTypes/IProject'
|
|||
import ProjectService from '@/services/project'
|
||||
import {includesById} from '@/helpers/utils'
|
||||
|
||||
type ProjectFilterFunc = (p: IProject) => boolean
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<IProject[]>,
|
||||
default: () => [],
|
||||
},
|
||||
projectFilter: {
|
||||
type: Function as PropType<ProjectFilterFunc>,
|
||||
default: () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return (_: IProject) => true
|
||||
},
|
||||
},
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: IProject[]): void
|
||||
|
@ -58,6 +67,8 @@ async function findProjects(query: string) {
|
|||
const response = await projectService.getAll({}, {s: query}) as IProject[]
|
||||
|
||||
// Filter selected items from the results
|
||||
foundProjects.value = response.filter(({id}) => !includesById(projects.value, id))
|
||||
foundProjects.value = response
|
||||
.filter(({id}) => !includesById(projects.value, id))
|
||||
.filter(props.projectFilter)
|
||||
}
|
||||
</script>
|
|
@ -164,6 +164,7 @@
|
|||
v-model="entities.projects"
|
||||
@select="changeMultiselectFilter('projects', 'project_id')"
|
||||
@remove="changeMultiselectFilter('projects', 'project_id')"
|
||||
:project-filter="p => p.id > 0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -411,10 +412,10 @@ function prepareDate(filterName, variableName) {
|
|||
const endDate = new Date(params.value.filter_value[foundDateEnd])
|
||||
filters.value[variableName] = {
|
||||
dateFrom: !isNaN(startDate)
|
||||
? `${startDate.getFullYear()}-${startDate.getMonth() + 1}-${startDate.getDate()}`
|
||||
? `${startDate.getUTCFullYear()}-${startDate.getUTCMonth() + 1}-${startDate.getUTCDate()}`
|
||||
: params.value.filter_value[foundDateStart],
|
||||
dateTo: !isNaN(endDate)
|
||||
? `${endDate.getFullYear()}-${endDate.getMonth() + 1}-${endDate.getDate()}`
|
||||
? `${endDate.getUTCFullYear()}-${endDate.getUTCMonth() + 1}-${endDate.getUTCDate()}`
|
||||
: params.value.filter_value[foundDateEnd],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,13 +24,18 @@ export function useRouteWithModal() {
|
|||
// this is adapted from vue-router
|
||||
// https://github.com/vuejs/vue-router-next/blob/798cab0d1e21f9b4d45a2bd12b840d2c7415f38a/src/RouterView.ts#L125
|
||||
const routePropsOption = route.matched[0]?.props.default
|
||||
const routeProps = routePropsOption
|
||||
? routePropsOption === true
|
||||
? route.params
|
||||
: typeof routePropsOption === 'function'
|
||||
? routePropsOption(route)
|
||||
: routePropsOption
|
||||
: {}
|
||||
let routeProps = undefined
|
||||
if (routePropsOption) {
|
||||
if (routePropsOption === true) {
|
||||
routeProps = route.params
|
||||
} else {
|
||||
if(typeof routePropsOption === 'function') {
|
||||
routeProps = routePropsOption(route)
|
||||
} else {
|
||||
routeProps = routePropsOption
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof routeProps === 'undefined') {
|
||||
currentModal.value = undefined
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
export function parseDateOrString(rawValue: string | undefined, fallback: unknown) {
|
||||
if (typeof rawValue === 'undefined') {
|
||||
export function parseDateOrString(rawValue: string | undefined | null, fallback: unknown): (unknown | string | Date) {
|
||||
if (rawValue === null || typeof rawValue === 'undefined') {
|
||||
return fallback
|
||||
}
|
||||
|
||||
if (rawValue.toLowerCase().includes('now') || rawValue.toLowerCase().includes('||')) {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
const d = new Date(rawValue)
|
||||
|
||||
return !isNaN(+d)
|
||||
|
|
|
@ -37,7 +37,11 @@ const MigrationHandlerComponent = () => import('@/views/migrate/MigrationHandler
|
|||
const ProjectList = () => import('@/views/project/ProjectList.vue')
|
||||
const ProjectGantt = () => import('@/views/project/ProjectGantt.vue')
|
||||
const ProjectTable = () => import('@/views/project/ProjectTable.vue')
|
||||
const ProjectKanban = () => import('@/views/project/ProjectKanban.vue')
|
||||
// If we load the component async, using it as a backdrop view will not work. Instead, everything explodes
|
||||
// with an error from the core saying "Cannot read properties of undefined (reading 'parentNode')"
|
||||
// Of course, with no clear indicator of where the problem comes from.
|
||||
// const ProjectKanban = () => import('@/views/project/ProjectKanban.vue')
|
||||
import ProjectKanban from '@/views/project/ProjectKanban.vue'
|
||||
const ProjectInfo = () => import('@/views/project/ProjectInfo.vue')
|
||||
|
||||
// Project Settings
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<ProjectWrapper
|
||||
class="project-kanban"
|
||||
:project-id="project.id"
|
||||
:project-id="projectId"
|
||||
viewName="kanban"
|
||||
>
|
||||
<template #header>
|
||||
|
@ -330,9 +330,14 @@ const bucketDraggableComponentData = computed(() => ({
|
|||
{'dragging-disabled': !canWrite.value},
|
||||
],
|
||||
}))
|
||||
const {
|
||||
projectId = undefined,
|
||||
} = defineProps<{
|
||||
projectId: number,
|
||||
}>()
|
||||
|
||||
const canWrite = computed(() => baseStore.currentProject?.maxRight > Rights.READ)
|
||||
const project = computed(() => baseStore.currentProject)
|
||||
const project = computed(() => projectId ? projectStore.projects[projectId]: null)
|
||||
|
||||
const buckets = computed(() => kanbanStore.buckets)
|
||||
const loading = computed(() => kanbanStore.isLoading)
|
||||
|
@ -342,10 +347,9 @@ const taskLoading = computed(() => taskStore.isLoading)
|
|||
watch(
|
||||
() => ({
|
||||
params: params.value,
|
||||
project: project.value,
|
||||
projectId,
|
||||
}),
|
||||
({params, project}) => {
|
||||
const projectId = project.id
|
||||
({params}) => {
|
||||
if (projectId === undefined || Number(projectId) === 0) {
|
||||
return
|
||||
}
|
||||
|
@ -396,7 +400,7 @@ function updateTasks(bucketId: IBucket['id'], tasks: IBucket['tasks']) {
|
|||
async function updateTaskPosition(e) {
|
||||
drag.value = false
|
||||
|
||||
// While we could just pass the bucket index in through the function call, this would not give us the
|
||||
// While we could just pass the bucket index in through the function call, this would not give us the
|
||||
// new bucket id when a task has been moved between buckets, only the new bucket. Using the data-bucket-id
|
||||
// of the drop target works all the time.
|
||||
const bucketIndex = parseInt(e.to.dataset.bucketIndex)
|
||||
|
@ -450,7 +454,7 @@ async function updateTaskPosition(e) {
|
|||
|
||||
try {
|
||||
await taskStore.update(newTask)
|
||||
|
||||
|
||||
// Make sure the first and second task don't both get position 0 assigned
|
||||
if(newTaskIndex === 0 && taskAfter !== null && taskAfter.kanbanPosition === 0) {
|
||||
const taskAfterAfter = newBucket.tasks[newTaskIndex + 2] ?? null
|
||||
|
@ -480,7 +484,7 @@ async function addTaskToBucket(bucketId: IBucket['id']) {
|
|||
return
|
||||
}
|
||||
newTaskError.value[bucketId] = false
|
||||
|
||||
|
||||
const task = await taskStore.createNewTask({
|
||||
title: newTaskText.value,
|
||||
bucketId,
|
||||
|
@ -619,7 +623,7 @@ async function toggleDoneBucket(bucket: IBucket) {
|
|||
const doneBucketId = project.value.doneBucketId === bucket.id
|
||||
? 0
|
||||
: bucket.id
|
||||
|
||||
|
||||
await projectStore.updateProject({
|
||||
...project.value,
|
||||
doneBucketId,
|
||||
|
@ -722,7 +726,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
|||
}
|
||||
&:last-of-type {
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-move {
|
||||
|
|
|
@ -182,7 +182,7 @@ async function loadPendingTasks(from: string, to: string) {
|
|||
}
|
||||
}
|
||||
|
||||
if (authStore.settings.frontendSettings.filterIdUsedOnOverview && typeof projectStore.projects[authStore.settings.frontendSettings.filterIdUsedOnOverview] !== 'undefined') {
|
||||
if (showAll.value && authStore.settings.frontendSettings.filterIdUsedOnOverview && typeof projectStore.projects[authStore.settings.frontendSettings.filterIdUsedOnOverview] !== 'undefined') {
|
||||
tasks.value = await taskStore.loadTasks(params, authStore.settings.frontendSettings.filterIdUsedOnOverview)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -24,25 +24,6 @@
|
|||
</label>
|
||||
<project-search v-model="filterUsedInOverview" :saved-filters-only="true"/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="settings.overdueTasksRemindersEnabled"/>
|
||||
{{ $t('user.settings.general.overdueReminders') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="field" v-if="settings.overdueTasksRemindersEnabled">
|
||||
<label class="label" for="overdueTasksReminderTime">
|
||||
{{ $t('user.settings.general.overdueTasksRemindersTime') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
@keyup.enter="updateSettings"
|
||||
class="input"
|
||||
id="overdueTasksReminderTime"
|
||||
type="time"
|
||||
v-model="settings.overdueTasksRemindersTime"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="settings.emailRemindersEnabled"/>
|
||||
|
@ -67,6 +48,25 @@
|
|||
{{ $t('user.settings.general.playSoundWhenDone') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="settings.overdueTasksRemindersEnabled"/>
|
||||
{{ $t('user.settings.general.overdueReminders') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="field" v-if="settings.overdueTasksRemindersEnabled">
|
||||
<label class="label" for="overdueTasksReminderTime">
|
||||
{{ $t('user.settings.general.overdueTasksRemindersTime') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
@keyup.enter="updateSettings"
|
||||
class="input"
|
||||
id="overdueTasksReminderTime"
|
||||
type="time"
|
||||
v-model="settings.overdueTasksRemindersTime"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="is-flex is-align-items-center">
|
||||
<span>
|
||||
|
|
Loading…
Reference in New Issue