Compare commits

..

41 Commits

Author SHA1 Message Date
kolaente 248d2fe563
chore(deps): sign drone config 2024-04-17 22:05:18 +02:00
renovate 0b36d28301
chore(deps): update dependency node to v20.12.2 2024-04-17 22:04:56 +02:00
renovate da8cee0ba5 fix(deps): update dependency vue to v3.4.23
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2024-04-17 10:07:15 +00:00
renovate 352381f377 chore(deps): update pnpm to v8.15.7
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-17 07:06:42 +00:00
renovate 61455b8795 fix(deps): update sentry-javascript monorepo to v7.110.1
continuous-integration/drone/push Build is passing Details
2024-04-17 06:21:21 +00:00
treysullivent aceaccbf11 docs: fix typo in README.md (#2271)
continuous-integration/drone/push Build is failing Details
Fixed "exausted" to "exhausted"

Reviewed-on: #2271
Reviewed-by: konrad <k@knt.li>
Co-authored-by: treysullivent <trey.sullivent@gmail.com>
Co-committed-by: treysullivent <trey.sullivent@gmail.com>
2024-04-17 06:20:44 +00:00
kolaente 392ce66edb
chore(deps): update github.com/adlio/trello to v1.12.0
continuous-integration/drone/push Build is passing Details
2024-04-16 23:14:51 +02:00
kolaente ecbefdb921
fix(buckets): return correct task count for tasks in buckets
continuous-integration/drone/push Build is passing Details
2024-04-14 17:21:53 +02:00
kolaente d8ca1a2de1
fix(favorites): make favorites work with configurable views
continuous-integration/drone/push Build is failing Details
2024-04-14 17:12:16 +02:00
kolaente 2d084c091e
feat: new login image
continuous-integration/drone/push Build is passing Details
2024-04-14 12:43:22 +02:00
kolaente 5a84d37fca
fix(kanban): do not focus on task list in bucket when clicking on a task
continuous-integration/drone/push Build is passing Details
2024-04-14 11:21:59 +02:00
kolaente fd520dab0a
fix(reminders): do not fall back to hours when the reminder interval is minutes
Resolves https://github.com/go-vikunja/vikunja/issues/225
2024-04-14 11:20:20 +02:00
kolaente 144a6e4140
fix(kanban): do not add bottom spacing to view
continuous-integration/drone/push Build is failing Details
2024-04-14 11:15:53 +02:00
kolaente a7aa74227a
fix(kanban): do not focus kanban board
continuous-integration/drone/push Build is failing Details
2024-04-14 11:12:26 +02:00
kolaente d2adbc53c6
fix(test): add task to bucket in test
continuous-integration/drone/push Build is failing Details
2024-04-14 11:00:41 +02:00
kolaente 422e4371f8
fix(project): add more spacing between filter button and view switcher on mobile
continuous-integration/drone/push Build is failing Details
2024-04-14 00:06:26 +02:00
kolaente 6e5b31f1e0
fix(filters): always persist filter or search in query path and load it correctly into filter query input when loading the page
continuous-integration/drone/push Build is failing Details
Previously, when using the filter query as a search input, it would load the search as requested but the filter query parameter in the url would be empty, which meant the search would not be loaded correctly when reloading (or otherwise newly accessing) the page. We're now persisting the filter and search in the task loading logic, to make sure they are always populated correctly.
2024-04-13 23:34:25 +02:00
kolaente 5756da412b
fix(project): return full project after duplicating it
continuous-integration/drone/push Build is failing Details
2024-04-13 22:39:40 +02:00
kolaente 4e05b8e97c
fix(project): do not crash when duplicating a project with no tasks
continuous-integration/drone/push Build is failing Details
2024-04-13 22:36:41 +02:00
kolaente 5177f516c4
fix(views): make sure view changes are reflected in switcher
continuous-integration/drone/push Build is failing Details
2024-04-13 22:24:12 +02:00
kolaente 637c8f6ba5
fix(views): make sure the view is saved properly in localStorage
continuous-integration/drone/push Build is failing Details
2024-04-13 22:15:41 +02:00
kolaente 1460d212ee
fix: do not push nil errors to sentry
continuous-integration/drone/push Build is failing Details
2024-04-13 21:46:07 +02:00
kolaente e9de7d8a24
fix(project): delete all related entities when deleting a project
continuous-integration/drone/push Build is failing Details
2024-04-13 21:43:44 +02:00
kolaente ce1d7778c7
fix(export): make export work with project views and new task positions
continuous-integration/drone/push Build is passing Details
2024-04-13 21:07:06 +02:00
kolaente 9a16f6f817
fix: license in cmd help text
continuous-integration/drone/push Build is passing Details
2024-04-13 20:13:24 +02:00
kolaente 7d755fcb89
fix: lint
continuous-integration/drone/push Build is passing Details
2024-04-13 17:58:53 +02:00
kolaente 77e95642a9
fix(tasks): make fetching tasks in buckets via typesense work
continuous-integration/drone/push Build is failing Details
2024-04-13 17:52:47 +02:00
kolaente a5d02380a3
fix(typesense): make fetching task positions per view more efficient
continuous-integration/drone/push Build is failing Details
2024-04-13 17:26:38 +02:00
kolaente 3519b8b2fe
fix(tasks): index and order by task position when using typesense 2024-04-13 17:19:27 +02:00
kolaente cb648e5ad8
fix(typesense): fix reindexing views and positions in typesense 2024-04-13 16:38:45 +02:00
kolaente 75f830457b
fix(comments): order comments by created timestamp instead of id
continuous-integration/drone/push Build is failing Details
Partially resolves https://community.vikunja.io/t/trello-import-comments-and-assignments/2174/14
2024-04-13 14:45:12 +02:00
kolaente 6e2b540394
fix(migration): import task comments with original timestamps
Partially resolves https://community.vikunja.io/t/trello-import-comments-and-assignments/2174/14
2024-04-13 14:44:55 +02:00
kolaente bf3c8ac9da
fix(views): check if bucket index already exists before creating new index
continuous-integration/drone/push Build is failing Details
Resolves #2243
2024-04-13 14:20:27 +02:00
kolaente 3e7225ebee
fix(editor): do not prevent shift+enter to add a line break in text
continuous-integration/drone/push Build is failing Details
Resolves https://github.com/go-vikunja/vikunja/issues/250
2024-04-13 14:08:27 +02:00
kolaente 9eb19e0362
fix(project): do not crash when views were not loaded yet
continuous-integration/drone/push Build is passing Details
The project view crashed when accessing a task from /projects because the currentProject in store was not set, hence the views weren't set either. This change adds a fallback to it.

Related to #2246
Related to https://community.vikunja.io/t/vikunja-freezes/2246/5
2024-04-13 13:18:14 +02:00
kolaente 73bf119409
docs: clarify version checkout when building from source
continuous-integration/drone/push Build is passing Details
Related to #2270 (comment)
2024-04-12 23:39:27 +02:00
kolaente 500b761fe6
fix(projects): do not return parent project id when authenticating as link share
continuous-integration/drone/push Build is passing Details
Related to https://community.vikunja.io/t/vikunja-freezes/2246
Related to https://github.com/go-vikunja/vikunja/issues/233
2024-04-12 18:02:39 +02:00
kolaente 0bc9a670d7
fix(task): do not crash when loading a task if parent projects are not loaded
continuous-integration/drone/push Build is failing Details
Related to https://community.vikunja.io/t/vikunja-freezes/2246
Related to https://github.com/go-vikunja/vikunja/issues/233
2024-04-12 17:56:19 +02:00
renovate a3e5e98c64 fix(deps): update module github.com/arran4/golang-ical to v0.2.8
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-04-11 16:07:49 +00:00
Elscrux a3a4d05e89 feat(editor): checklist visual improvements (#2264)
continuous-integration/drone/push Build is passing Details
This makes task lists (especially big ones) easier to read. I've set a margin so there is a distance between task items which makes them easier to stand out.
I've also changed the visuals of the checked elements (strike through + grey font color) so the unchecked ones stand out more. Note that this currently seems to be a big bugged outside of edit mode as `data-checked` doesn't seem to be updating correctly in this state which seems to be an issue that is already noted for the TipTap editor.

Co-authored-by: Elscrux <nickposer2102@gmail.com>
Reviewed-on: #2264
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Elscrux <elscrux@gmail.com>
Co-committed-by: Elscrux <elscrux@gmail.com>
2024-04-11 15:46:10 +00:00
renovate 72c3e1a03f chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build encountered an error Details
2024-04-11 00:09:10 +00:00
39 changed files with 803 additions and 308 deletions

View File

@ -1400,6 +1400,6 @@ steps:
- failure
---
kind: signature
hmac: 2c9cb0483fb346988188515f6423929f46eefb9e14eb26b0f312a0b694d5fe8c
hmac: f2753482faf9e2a3d34a9111587a75dfb4519cb77002cc64a51266540fd2478e
...

View File

@ -104,3 +104,7 @@ issues:
text: "parameter 'tx' seems to be unused, consider removing or renaming it as"
linters:
- revive
- path: pkg/models/typesense.go
text: 'structtag: struct field Position repeats json tag "position" also at'
linters:
- govet

View File

@ -29,7 +29,7 @@ If you find any security-related issues you don't want to disclose publicly, ple
## Features
See [the features page](https://vikunja.io/features/) on our website for a more exaustive list or
See [the features page](https://vikunja.io/features/) on our website for a more exhaustive list or
try it on [try.vikunja.io](https://try.vikunja.io)!
## Docs

View File

@ -51,7 +51,7 @@
}
},
"devDependencies": {
"electron": "29.2.0",
"electron": "29.3.0",
"electron-builder": "24.13.3"
},
"dependencies": {

View File

@ -769,10 +769,10 @@ electron-publish@24.13.1:
lazy-val "^1.0.5"
mime "^2.5.2"
electron@29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.2.0.tgz#98e9d45dcebda124fb0bd1ff20fc509ec692101c"
integrity sha512-ALKrCN52RG4g9prx4DriXSPnY5WoiyRUCNp7zEVQuoiNOpHTNqMMpRidQAHzntV4hajF1LMWHVoBkwqIs1jHhg==
electron@29.3.0:
version "29.3.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.3.0.tgz#8e65cb08e9c0952c66d3196e1b5c811c43b8c5b0"
integrity sha512-ZxFKm0/v48GSoBuO3DdnMlCYXefEUKUHLMsKxyXY4nZGgzbBKpF/X8haZa2paNj23CLfsCKBOtfc2vsEQiOOsA==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"

View File

@ -18,6 +18,7 @@ To fully build Vikunja from source files, you need to build the api and frontend
1. Make sure you have git installed
2. Clone the repo with `git clone https://code.vikunja.io/vikunja` and switch into the directory.
3. Check out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use. If you don't do this, you'll build the [latest unstable build]({{< ref "versions.md">}}), which might contain bugs.
## Frontend

View File

@ -225,10 +225,13 @@ go install github.com/magefile/mage
```
mkdir /mnt/GO/code.vikunja.io
cd /mnt/GO/code.vikunja.io
git clone https://code.vikunja.io/api
cd /mnt/GO/code.vikunja.io/api
git clone https://code.vikunja.io/vikunja
cd vikunja
```
**Note:** Ceck out the version you want to build with `git checkout VERSION` - replace `VERSION` with the version want to use.
If you don't do this, you'll build the [latest unstable build]({{< ref "versions.md">}}), which might contain bugs.
### Compile binaries
```

View File

@ -13,6 +13,7 @@ import {BucketFactory} from '../../factories/bucket'
import {TaskAttachmentFactory} from '../../factories/task_attachments'
import {TaskReminderFactory} from '../../factories/task_reminders'
import {createDefaultViews} from "../project/prepareProjects";
import { TaskBucketFactory } from '../../factories/task_buckets'
function addLabelToTaskAndVerify(labelTitle: string) {
cy.get('.task-view .action-buttons .button')
@ -48,7 +49,7 @@ function uploadAttachmentAndVerify(taskId: number) {
describe('Task', () => {
createFakeUserAndLogin()
let projects
let projects: {}[]
let buckets
beforeEach(() => {
@ -470,6 +471,10 @@ describe('Task', () => {
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
TaskBucketFactory.create(1, {
task_id: tasks[0].id,
bucket_id: buckets[0].id,
})
cy.visit(`/projects/${projects[0].id}/4`)

View File

@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@8.15.6",
"packageManager": "pnpm@8.15.7",
"keywords": [
"todo",
"productivity",
@ -58,8 +58,8 @@
"@infectoone/vue-ganttastic": "2.3.2",
"@intlify/unplugin-vue-i18n": "3.0.1",
"@kyvg/vue3-notification": "3.2.1",
"@sentry/tracing": "7.109.0",
"@sentry/vue": "7.109.0",
"@sentry/tracing": "7.110.1",
"@sentry/vue": "7.110.1",
"@tiptap/core": "2.3.0",
"@tiptap/extension-blockquote": "2.3.0",
"@tiptap/extension-bold": "2.3.0",
@ -118,7 +118,7 @@
"sortablejs": "1.15.2",
"tippy.js": "6.3.7",
"ufo": "1.5.3",
"vue": "3.4.21",
"vue": "3.4.23",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "9.11.1",
@ -134,7 +134,7 @@
"@faker-js/faker": "8.4.1",
"@histoire/plugin-screenshot": "0.17.17",
"@histoire/plugin-vue": "0.17.17",
"@rushstack/eslint-patch": "1.10.1",
"@rushstack/eslint-patch": "1.10.2",
"@tsconfig/node18": "18.2.4",
"@types/codemirror": "5.60.15",
"@types/dompurify": "3.0.5",

View File

@ -24,25 +24,25 @@ dependencies:
version: 6.5.2
'@fortawesome/vue-fontawesome':
specifier: 3.0.6
version: 3.0.6(@fortawesome/fontawesome-svg-core@6.5.2)(vue@3.4.21)
version: 3.0.6(@fortawesome/fontawesome-svg-core@6.5.2)(vue@3.4.23)
'@github/hotkey':
specifier: 3.1.0
version: 3.1.0(patch_hash=c67tdk7qpd5grxd2zj6lsxfbou)
'@infectoone/vue-ganttastic':
specifier: 2.3.2
version: 2.3.2(dayjs@1.11.10)(vue@3.4.21)
version: 2.3.2(dayjs@1.11.10)(vue@3.4.23)
'@intlify/unplugin-vue-i18n':
specifier: 3.0.1
version: 3.0.1(rollup@4.14.1)(vue-i18n@9.11.1)
'@kyvg/vue3-notification':
specifier: 3.2.1
version: 3.2.1(vue@3.4.21)
version: 3.2.1(vue@3.4.23)
'@sentry/tracing':
specifier: 7.109.0
version: 7.109.0
specifier: 7.110.1
version: 7.110.1
'@sentry/vue':
specifier: 7.109.0
version: 7.109.0(vue@3.4.21)
specifier: 7.110.1
version: 7.110.1(vue@3.4.23)
'@tiptap/core':
specifier: 2.3.0
version: 2.3.0(@tiptap/pm@2.3.0)
@ -141,7 +141,7 @@ dependencies:
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/vue-3':
specifier: 2.3.0
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)(vue@3.4.21)
version: 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)(vue@3.4.23)
'@types/is-touch-device':
specifier: 1.0.2
version: 1.0.2
@ -150,10 +150,10 @@ dependencies:
version: 4.5.9
'@vueuse/core':
specifier: 10.9.0
version: 10.9.0(vue@3.4.21)
version: 10.9.0(vue@3.4.23)
'@vueuse/router':
specifier: 10.9.0
version: 10.9.0(vue-router@4.3.0)(vue@3.4.21)
version: 10.9.0(vue-router@4.3.0)(vue@3.4.23)
axios:
specifier: 1.6.8
version: 1.6.8(debug@4.3.4)
@ -186,7 +186,7 @@ dependencies:
version: 0.7.31(patch_hash=bfn3sngfuhktmdj7jgl3ejl35y)
floating-vue:
specifier: 5.2.2
version: 5.2.2(vue@3.4.21)
version: 5.2.2(vue@3.4.23)
is-touch-device:
specifier: 1.0.1
version: 1.0.1
@ -201,7 +201,7 @@ dependencies:
version: 2.9.0
pinia:
specifier: 2.1.7
version: 2.1.7(typescript@5.4.5)(vue@3.4.21)
version: 2.1.7(typescript@5.4.5)(vue@3.4.23)
register-service-worker:
specifier: 1.7.2
version: 1.7.2
@ -218,29 +218,29 @@ dependencies:
specifier: 1.5.3
version: 1.5.3
vue:
specifier: 3.4.21
version: 3.4.21(typescript@5.4.5)
specifier: 3.4.23
version: 3.4.23(typescript@5.4.5)
vue-advanced-cropper:
specifier: 2.8.8
version: 2.8.8(vue@3.4.21)
version: 2.8.8(vue@3.4.23)
vue-flatpickr-component:
specifier: 11.0.5
version: 11.0.5(vue@3.4.21)
version: 11.0.5(vue@3.4.23)
vue-i18n:
specifier: 9.11.1
version: 9.11.1(vue@3.4.21)
version: 9.11.1(vue@3.4.23)
vue-router:
specifier: 4.3.0
version: 4.3.0(vue@3.4.21)
version: 4.3.0(vue@3.4.23)
vuemoji-picker:
specifier: 0.2.1
version: 0.2.1(vue@3.4.21)
version: 0.2.1(vue@3.4.23)
workbox-precaching:
specifier: 7.0.0
version: 7.0.0
zhyswan-vuedraggable:
specifier: 4.1.3
version: 4.1.3(vue@3.4.21)
version: 4.1.3(vue@3.4.23)
devDependencies:
'@4tw/cypress-drag-drop':
@ -251,7 +251,7 @@ devDependencies:
version: 5.0.7
'@cypress/vue':
specifier: 6.0.0
version: 6.0.0(cypress@13.7.2)(vue@3.4.21)
version: 6.0.0(cypress@13.7.2)(vue@3.4.23)
'@faker-js/faker':
specifier: 8.4.1
version: 8.4.1
@ -260,10 +260,10 @@ devDependencies:
version: 0.17.17(histoire@0.17.17)
'@histoire/plugin-vue':
specifier: 0.17.17
version: 0.17.17(histoire@0.17.17)(vite@5.2.8)(vue@3.4.21)
version: 0.17.17(histoire@0.17.17)(vite@5.2.8)(vue@3.4.23)
'@rushstack/eslint-patch':
specifier: 1.10.1
version: 1.10.1
specifier: 1.10.2
version: 1.10.2
'@tsconfig/node18':
specifier: 18.2.4
version: 18.2.4
@ -302,7 +302,7 @@ devDependencies:
version: 5.3.2(terser@5.24.0)(vite@5.2.8)
'@vitejs/plugin-vue':
specifier: 5.0.4
version: 5.0.4(vite@5.2.8)(vue@3.4.21)
version: 5.0.4(vite@5.2.8)(vue@3.4.23)
'@vue/eslint-config-typescript':
specifier: 13.0.0
version: 13.0.0(eslint-plugin-vue@9.24.1)(eslint@8.57.0)(typescript@5.4.5)
@ -389,7 +389,7 @@ devDependencies:
version: 1.4.0(vite@5.2.8)
vite-svg-loader:
specifier: 5.1.0
version: 5.1.0(vue@3.4.21)
version: 5.1.0(vue@3.4.23)
vitest:
specifier: 1.4.0
version: 1.4.0(@types/node@20.12.7)(happy-dom@14.7.1)(sass@1.74.1)(terser@5.24.0)
@ -713,6 +713,13 @@ packages:
dependencies:
'@babel/types': 7.23.9
/@babel/parser@7.24.4:
resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.23.9
/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.9):
resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==}
engines: {node: '>=6.9.0'}
@ -2154,7 +2161,7 @@ packages:
- supports-color
dev: true
/@cypress/vue@6.0.0(cypress@13.7.2)(vue@3.4.21):
/@cypress/vue@6.0.0(cypress@13.7.2)(vue@3.4.23):
resolution: {integrity: sha512-KMfRw8y/kXn/RJqaDdFnYnW7YLk47313UE3Yip+sLDjILJ2kA0WEiEa6MYKe58v8TNRtwcRpUH5xAYVNs1N6/A==}
engines: {node: '>=8'}
peerDependencies:
@ -2166,7 +2173,7 @@ packages:
optional: true
dependencies:
cypress: 13.7.2
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: true
/@cypress/xvfb@1.2.4(supports-color@8.1.1):
@ -2472,14 +2479,14 @@ packages:
'@fortawesome/fontawesome-common-types': 6.5.2
dev: false
/@fortawesome/vue-fontawesome@3.0.6(@fortawesome/fontawesome-svg-core@6.5.2)(vue@3.4.21):
/@fortawesome/vue-fontawesome@3.0.6(@fortawesome/fontawesome-svg-core@6.5.2)(vue@3.4.23):
resolution: {integrity: sha512-akrL7lTroyNpPkoHtvK2UpsMzJr6jXdHaQ0YdcwqDsB8jdwlpNHZYijpOUd9KJsARr+VB3WXY4EyObepqJ4ytQ==}
peerDependencies:
'@fortawesome/fontawesome-svg-core': ~1 || ~6
vue: '>= 3.0.0 < 4'
dependencies:
'@fortawesome/fontawesome-svg-core': 6.5.2
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/@github/hotkey@3.1.0(patch_hash=c67tdk7qpd5grxd2zj6lsxfbou):
@ -2543,7 +2550,7 @@ packages:
- utf-8-validate
dev: true
/@histoire/plugin-vue@0.17.17(histoire@0.17.17)(vite@5.2.8)(vue@3.4.21):
/@histoire/plugin-vue@0.17.17(histoire@0.17.17)(vite@5.2.8)(vue@3.4.23):
resolution: {integrity: sha512-O5h/Ww6IT2CygVVT4onN27IZt11Z2qE8XeHeXJCEese3dxnnVWRhjMpsaWAU5XqgfjKNAiALJk86b49/6NQaRg==}
peerDependencies:
histoire: ^0.17.17
@ -2557,7 +2564,7 @@ packages:
histoire: 0.17.17(@types/node@20.12.7)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8)
launch-editor: 2.6.1
pathe: 1.1.1
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
transitivePeerDependencies:
- vite
dev: true
@ -2600,15 +2607,15 @@ packages:
resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
dev: true
/@infectoone/vue-ganttastic@2.3.2(dayjs@1.11.10)(vue@3.4.21):
/@infectoone/vue-ganttastic@2.3.2(dayjs@1.11.10)(vue@3.4.23):
resolution: {integrity: sha512-krxHdlZvo4cdS4axQ99qb756RzwieI7LcyY2vAIehJ5Sxd/jz5Pu/vTplTC0Rxqj8T4v1knYPK9uvTMkQYWYng==}
peerDependencies:
dayjs: ^1.11.5
vue: ^3.2.40
dependencies:
'@vueuse/core': 9.13.0(vue@3.4.21)
'@vueuse/core': 9.13.0(vue@3.4.23)
dayjs: 1.11.10
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
transitivePeerDependencies:
- '@vue/composition-api'
dev: false
@ -2634,7 +2641,7 @@ packages:
magic-string: 0.30.7
mlly: 1.4.2
source-map-js: 1.2.0
vue-i18n: 9.11.1(vue@3.4.21)
vue-i18n: 9.11.1(vue@3.4.23)
yaml-eslint-parser: 1.2.2
dev: false
@ -2704,7 +2711,7 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
unplugin: 1.1.0
vue-i18n: 9.11.1(vue@3.4.21)
vue-i18n: 9.11.1(vue@3.4.23)
transitivePeerDependencies:
- rollup
- supports-color
@ -2761,12 +2768,12 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@kyvg/vue3-notification@3.2.1(vue@3.4.21):
/@kyvg/vue3-notification@3.2.1(vue@3.4.23):
resolution: {integrity: sha512-qn4bCBBCxW0Ya+RmHXu2SYBVwGXWsAGdlDKqCqyLryaZTbtFXi32iSSLnuKjSUVxFqQRToFc6g1zp1XLTyHrvw==}
peerDependencies:
vue: ^3.0.0
dependencies:
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/@lezer/common@1.1.1:
@ -3053,49 +3060,49 @@ packages:
requiresBuild: true
optional: true
/@rushstack/eslint-patch@1.10.1:
resolution: {integrity: sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg==}
/@rushstack/eslint-patch@1.10.2:
resolution: {integrity: sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==}
dev: true
/@sentry-internal/feedback@7.109.0:
resolution: {integrity: sha512-EL7N++poxvJP9rYvh6vSu24tsKkOveNCcCj4IM7+irWPjsuD2GLYYlhp/A/Mtt9l7iqO4plvtiQU5HGk7smcTQ==}
/@sentry-internal/feedback@7.110.1:
resolution: {integrity: sha512-0aR3wuEW+SZKOVNamuy0pTQyPmqDjWPPLrB2GAXGT3ZjrVxjEzzVPqk6DVBYxSV2MuJaD507SZnvfoSPNgoBmw==}
engines: {node: '>=12'}
dependencies:
'@sentry/core': 7.109.0
'@sentry/types': 7.109.0
'@sentry/utils': 7.109.0
'@sentry/core': 7.110.1
'@sentry/types': 7.110.1
'@sentry/utils': 7.110.1
dev: false
/@sentry-internal/replay-canvas@7.109.0:
resolution: {integrity: sha512-Lh/K60kmloR6lkPUcQP0iamw7B/MdEUEx/ImAx4tUSMrLj+IoUEcq/ECgnnVyQkJq59+8nPEKrVLt7x6PUPEjw==}
/@sentry-internal/replay-canvas@7.110.1:
resolution: {integrity: sha512-zdcCmWFXM4DHOau/BCZVb6jf9zozdbAiJ1MzQ6azuZEuysOl00YfktoWZBbZjjjpWT6025s+wrmFz54t0O+enw==}
engines: {node: '>=12'}
dependencies:
'@sentry/core': 7.109.0
'@sentry/replay': 7.109.0
'@sentry/types': 7.109.0
'@sentry/utils': 7.109.0
'@sentry/core': 7.110.1
'@sentry/replay': 7.110.1
'@sentry/types': 7.110.1
'@sentry/utils': 7.110.1
dev: false
/@sentry-internal/tracing@7.109.0:
resolution: {integrity: sha512-PzK/joC5tCuh2R/PRh+7dp+uuZl7pTsBIjPhVZHMTtb9+ls65WkdZJ1/uKXPouyz8NOo9Xok7aEvEo9seongyw==}
/@sentry-internal/tracing@7.110.1:
resolution: {integrity: sha512-4kTd6EM0OP1SVWl2yLn3KIwlCpld1lyhNDeR8G1aKLm1PN+kVsR6YB/jy9KPPp4Q3lN3W9EkTSES3qhP4jVffQ==}
engines: {node: '>=8'}
dependencies:
'@sentry/core': 7.109.0
'@sentry/types': 7.109.0
'@sentry/utils': 7.109.0
'@sentry/core': 7.110.1
'@sentry/types': 7.110.1
'@sentry/utils': 7.110.1
dev: false
/@sentry/browser@7.109.0:
resolution: {integrity: sha512-yx+OFG+Ab9qUDDgV9ZDv8M9O9Mqr0fjKta/LMlWALYLjzkMvxsPlRPFj7oMBlHqOTVLDeg7lFYmsA8wyWQ8Z8g==}
/@sentry/browser@7.110.1:
resolution: {integrity: sha512-H3TZlbdsgxuoVxhotMtBDemvAofx3UPNcS+UjQ40Bd+hKX01IIbEN3i+9RQ0jmcbU6xjf+yhjwp+Ejpm4FmYMw==}
engines: {node: '>=8'}
dependencies:
'@sentry-internal/feedback': 7.109.0
'@sentry-internal/replay-canvas': 7.109.0
'@sentry-internal/tracing': 7.109.0
'@sentry/core': 7.109.0
'@sentry/replay': 7.109.0
'@sentry/types': 7.109.0
'@sentry/utils': 7.109.0
'@sentry-internal/feedback': 7.110.1
'@sentry-internal/replay-canvas': 7.110.1
'@sentry-internal/tracing': 7.110.1
'@sentry/core': 7.110.1
'@sentry/replay': 7.110.1
'@sentry/types': 7.110.1
'@sentry/utils': 7.110.1
dev: false
/@sentry/cli@2.19.1:
@ -3114,54 +3121,54 @@ packages:
- supports-color
dev: true
/@sentry/core@7.109.0:
resolution: {integrity: sha512-xwD4U0IlvvlE/x/g/W1I8b4Cfb16SsCMmiEuBf6XxvAa3OfWBxKoqLifb3GyrbxMC4LbIIZCN/SvLlnGJPgszA==}
/@sentry/core@7.110.1:
resolution: {integrity: sha512-yC1yeUFQlmHj9u/KxKmwOMVanBmgfX+4MZnZU31QPqN95adyZTwpaYFZl4fH5kDVnz7wXJI0qRP8SxuMePtqhw==}
engines: {node: '>=8'}
dependencies:
'@sentry/types': 7.109.0
'@sentry/utils': 7.109.0
'@sentry/types': 7.110.1
'@sentry/utils': 7.110.1
dev: false
/@sentry/replay@7.109.0:
resolution: {integrity: sha512-hCDjbTNO7ErW/XsaBXlyHFsUhneyBUdTec1Swf98TFEfVqNsTs6q338aUcaR8dGRLbLrJ9YU9D1qKq++v5h2CA==}
/@sentry/replay@7.110.1:
resolution: {integrity: sha512-R49fGOuKYsJ97EujPTzMjs3ZSuSkLTFFQmVBbsu/o6beRp4kK9l8H7r2BfLEcWJOXdWO5EU4KpRWgIxHaDK2aw==}
engines: {node: '>=12'}
dependencies:
'@sentry-internal/tracing': 7.109.0
'@sentry/core': 7.109.0
'@sentry/types': 7.109.0
'@sentry/utils': 7.109.0
'@sentry-internal/tracing': 7.110.1
'@sentry/core': 7.110.1
'@sentry/types': 7.110.1
'@sentry/utils': 7.110.1
dev: false
/@sentry/tracing@7.109.0:
resolution: {integrity: sha512-lgveekdd/XLgQ6oZLvcRGUWBIOAZ7QwbjLHEd8zzeKubHbIwFzMkhIejskqLi2z2OwzrYl5q+Zo1jS1z9f62YQ==}
/@sentry/tracing@7.110.1:
resolution: {integrity: sha512-y9tFHCV/8PeuV9TBOyMJ9w1vVOhkzoHy9d78eO5N3Q1lC+wImJSEBxb6aPcZ+RkuFBKSMm7fZTF3OFA32dHHLA==}
engines: {node: '>=8'}
dependencies:
'@sentry-internal/tracing': 7.109.0
'@sentry-internal/tracing': 7.110.1
dev: false
/@sentry/types@7.109.0:
resolution: {integrity: sha512-egCBnDv3YpVFoNzRLdP0soVrxVLCQ+rovREKJ1sw3rA2/MFH9WJ+DZZexsX89yeAFzy1IFsCp7/dEqudusml6g==}
/@sentry/types@7.110.1:
resolution: {integrity: sha512-sZxOpM5gfyxvJeWVvNpHnxERTnlqcozjqNcIv29SZ6wonlkekmxDyJ3uCuPv85VO54WLyA4uzskPKnNFHacI8A==}
engines: {node: '>=8'}
dev: false
/@sentry/utils@7.109.0:
resolution: {integrity: sha512-3RjxMOLMBwZ5VSiH84+o/3NY2An4Zldjz0EbfEQNRY9yffRiCPJSQiCJID8EoylCFOh/PAhPimBhqbtWJxX6iw==}
/@sentry/utils@7.110.1:
resolution: {integrity: sha512-eibLo2m1a7sHkOHxYYmRujr3D7ek2l9sv26F1SLoQBVDF7Afw5AKyzPmtA1D+4M9P/ux1okj7cGj3SaBrVpxXA==}
engines: {node: '>=8'}
dependencies:
'@sentry/types': 7.109.0
'@sentry/types': 7.110.1
dev: false
/@sentry/vue@7.109.0(vue@3.4.21):
resolution: {integrity: sha512-aATuLXJB7YJaezedwP71sGeVabt9moyvxlZZdTU+LdgTizdMZk3zvWF/Uen6HGCgvKerDYt52zSHWSQvDHCvLw==}
/@sentry/vue@7.110.1(vue@3.4.23):
resolution: {integrity: sha512-+dS5gfr360wiWgdRMPgJHl+mF/44U1/H5CYwTL/a1X7psRY4NEQSo7oltAGeZc723hmlJubxmwJYU4xBWgaasw==}
engines: {node: '>=8'}
peerDependencies:
vue: 2.x || 3.x
dependencies:
'@sentry/browser': 7.109.0
'@sentry/core': 7.109.0
'@sentry/types': 7.109.0
'@sentry/utils': 7.109.0
vue: 3.4.21(typescript@5.4.5)
'@sentry/browser': 7.110.1
'@sentry/core': 7.110.1
'@sentry/types': 7.110.1
'@sentry/utils': 7.110.1
vue: 3.4.23(typescript@5.4.5)
dev: false
/@sideway/address@4.1.3:
@ -3529,7 +3536,7 @@ packages:
'@tiptap/pm': 2.3.0
dev: false
/@tiptap/vue-3@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)(vue@3.4.21):
/@tiptap/vue-3@2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)(vue@3.4.23):
resolution: {integrity: sha512-Jgsoouq7gD6SkUf7McOJnKOHqVTVDJkPqhXZUZyJbJ22wD+7drxlauWwWexEymbs95ByhKblreLwcumvbOztgg==}
peerDependencies:
'@tiptap/core': ^2.0.0
@ -3540,7 +3547,7 @@ packages:
'@tiptap/extension-bubble-menu': 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/extension-floating-menu': 2.3.0(@tiptap/core@2.3.0)(@tiptap/pm@2.3.0)
'@tiptap/pm': 2.3.0
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/@tootallnate/once@2.0.0:
@ -3913,7 +3920,7 @@ packages:
- supports-color
dev: true
/@vitejs/plugin-vue@5.0.4(vite@5.2.8)(vue@3.4.21):
/@vitejs/plugin-vue@5.0.4(vite@5.2.8)(vue@3.4.23):
resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
@ -3921,7 +3928,7 @@ packages:
vue: ^3.2.25
dependencies:
vite: 5.2.8(@types/node@20.12.7)(sass@1.74.1)(terser@5.24.0)
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: true
/@vitest/expect@1.4.0:
@ -3991,12 +3998,27 @@ packages:
estree-walker: 2.0.2
source-map-js: 1.0.2
/@vue/compiler-core@3.4.23:
resolution: {integrity: sha512-HAFmuVEwNqNdmk+w4VCQ2pkLk1Vw4XYiiyxEp3z/xvl14aLTUBw2OfVH3vBcx+FtGsynQLkkhK410Nah1N2yyQ==}
dependencies:
'@babel/parser': 7.24.4
'@vue/shared': 3.4.23
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.0
/@vue/compiler-dom@3.4.21:
resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==}
dependencies:
'@vue/compiler-core': 3.4.21
'@vue/shared': 3.4.21
/@vue/compiler-dom@3.4.23:
resolution: {integrity: sha512-t0b9WSTnCRrzsBGrDd1LNR5HGzYTr7LX3z6nNBG+KGvZLqrT0mY6NsMzOqlVMBKKXKVuusbbB5aOOFgTY+senw==}
dependencies:
'@vue/compiler-core': 3.4.23
'@vue/shared': 3.4.23
/@vue/compiler-sfc@3.4.21:
resolution: {integrity: sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==}
dependencies:
@ -4009,12 +4031,33 @@ packages:
magic-string: 0.30.7
postcss: 8.4.38
source-map-js: 1.2.0
dev: false
/@vue/compiler-sfc@3.4.23:
resolution: {integrity: sha512-fSDTKTfzaRX1kNAUiaj8JB4AokikzStWgHooMhaxyjZerw624L+IAP/fvI4ZwMpwIh8f08PVzEnu4rg8/Npssw==}
dependencies:
'@babel/parser': 7.24.4
'@vue/compiler-core': 3.4.23
'@vue/compiler-dom': 3.4.23
'@vue/compiler-ssr': 3.4.23
'@vue/shared': 3.4.23
estree-walker: 2.0.2
magic-string: 0.30.9
postcss: 8.4.38
source-map-js: 1.2.0
/@vue/compiler-ssr@3.4.21:
resolution: {integrity: sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==}
dependencies:
'@vue/compiler-dom': 3.4.21
'@vue/shared': 3.4.21
dev: false
/@vue/compiler-ssr@3.4.23:
resolution: {integrity: sha512-hb6Uj2cYs+tfqz71Wj6h3E5t6OKvb4MVcM2Nl5i/z1nv1gjEhw+zYaNOV+Xwn+SSN/VZM0DgANw5TuJfxfezPg==}
dependencies:
'@vue/compiler-dom': 3.4.23
'@vue/shared': 3.4.23
/@vue/devtools-api@6.5.0:
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
@ -4063,36 +4106,39 @@ packages:
vue-template-compiler: 2.7.14
dev: true
/@vue/reactivity@3.4.21:
resolution: {integrity: sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==}
/@vue/reactivity@3.4.23:
resolution: {integrity: sha512-GlXR9PL+23fQ3IqnbSQ8OQKLodjqCyoCrmdLKZk3BP7jN6prWheAfU7a3mrltewTkoBm+N7qMEb372VHIkQRMQ==}
dependencies:
'@vue/shared': 3.4.21
'@vue/shared': 3.4.23
/@vue/runtime-core@3.4.21:
resolution: {integrity: sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==}
/@vue/runtime-core@3.4.23:
resolution: {integrity: sha512-FeQ9MZEXoFzFkFiw9MQQ/FWs3srvrP+SjDKSeRIiQHIhtkzoj0X4rWQlRNHbGuSwLra6pMyjAttwixNMjc/xLw==}
dependencies:
'@vue/reactivity': 3.4.21
'@vue/shared': 3.4.21
'@vue/reactivity': 3.4.23
'@vue/shared': 3.4.23
/@vue/runtime-dom@3.4.21:
resolution: {integrity: sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==}
/@vue/runtime-dom@3.4.23:
resolution: {integrity: sha512-RXJFwwykZWBkMiTPSLEWU3kgVLNAfActBfWFlZd0y79FTUxexogd0PLG4HH2LfOktjRxV47Nulygh0JFXe5f9A==}
dependencies:
'@vue/runtime-core': 3.4.21
'@vue/shared': 3.4.21
'@vue/runtime-core': 3.4.23
'@vue/shared': 3.4.23
csstype: 3.1.3
/@vue/server-renderer@3.4.21(vue@3.4.21):
resolution: {integrity: sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==}
/@vue/server-renderer@3.4.23(vue@3.4.23):
resolution: {integrity: sha512-LDwGHtnIzvKFNS8dPJ1SSU5Gvm36p2ck8wCZc52fc3k/IfjKcwCyrWEf0Yag/2wTFUBXrqizfhK9c/mC367dXQ==}
peerDependencies:
vue: 3.4.21
vue: 3.4.23
dependencies:
'@vue/compiler-ssr': 3.4.21
'@vue/shared': 3.4.21
vue: 3.4.21(typescript@5.4.5)
'@vue/compiler-ssr': 3.4.23
'@vue/shared': 3.4.23
vue: 3.4.23(typescript@5.4.5)
/@vue/shared@3.4.21:
resolution: {integrity: sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==}
/@vue/shared@3.4.23:
resolution: {integrity: sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg==}
/@vue/test-utils@2.4.5:
resolution: {integrity: sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==}
dependencies:
@ -4104,25 +4150,25 @@ packages:
resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==}
dev: true
/@vueuse/core@10.9.0(vue@3.4.21):
/@vueuse/core@10.9.0(vue@3.4.23):
resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==}
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 10.9.0
'@vueuse/shared': 10.9.0(vue@3.4.21)
vue-demi: 0.14.7(vue@3.4.21)
'@vueuse/shared': 10.9.0(vue@3.4.23)
vue-demi: 0.14.7(vue@3.4.23)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/core@9.13.0(vue@3.4.21):
/@vueuse/core@9.13.0(vue@3.4.23):
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
dependencies:
'@types/web-bluetooth': 0.0.16
'@vueuse/metadata': 9.13.0
'@vueuse/shared': 9.13.0(vue@3.4.21)
vue-demi: 0.14.7(vue@3.4.21)
'@vueuse/shared': 9.13.0(vue@3.4.23)
vue-demi: 0.14.7(vue@3.4.23)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
@ -4136,32 +4182,32 @@ packages:
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
dev: false
/@vueuse/router@10.9.0(vue-router@4.3.0)(vue@3.4.21):
/@vueuse/router@10.9.0(vue-router@4.3.0)(vue@3.4.23):
resolution: {integrity: sha512-MOmrCMQlRuPS4PExE1hy8T0XbZUXaNbEuh7CAG5mC8kdvdgANQMkdvJ7vIEOP27n5mXK/4YjvXJOZSsur4E0QQ==}
peerDependencies:
vue-router: '>=4.0.0-rc.1'
dependencies:
'@vueuse/shared': 10.9.0(vue@3.4.21)
vue-demi: 0.14.7(vue@3.4.21)
vue-router: 4.3.0(vue@3.4.21)
'@vueuse/shared': 10.9.0(vue@3.4.23)
vue-demi: 0.14.7(vue@3.4.23)
vue-router: 4.3.0(vue@3.4.23)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/shared@10.9.0(vue@3.4.21):
/@vueuse/shared@10.9.0(vue@3.4.23):
resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==}
dependencies:
vue-demi: 0.14.7(vue@3.4.21)
vue-demi: 0.14.7(vue@3.4.23)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/shared@9.13.0(vue@3.4.21):
/@vueuse/shared@9.13.0(vue@3.4.23):
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
dependencies:
vue-demi: 0.14.7(vue@3.4.21)
vue-demi: 0.14.7(vue@3.4.23)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
@ -5931,7 +5977,7 @@ packages:
dev: false
patched: true
/floating-vue@5.2.2(vue@3.4.21):
/floating-vue@5.2.2(vue@3.4.23):
resolution: {integrity: sha512-afW+h2CFafo+7Y9Lvw/xsqjaQlKLdJV7h1fCHfcYQ1C4SVMlu7OAekqWgu5d4SgvkBVU0pVpLlVsrSTBURFRkg==}
peerDependencies:
'@nuxt/kit': ^3.2.0
@ -5941,8 +5987,8 @@ packages:
optional: true
dependencies:
'@floating-ui/dom': 1.1.1
vue: 3.4.21(typescript@5.4.5)
vue-resize: 2.0.0-alpha.1(vue@3.4.21)
vue: 3.4.23(typescript@5.4.5)
vue-resize: 2.0.0-alpha.1(vue@3.4.23)
dev: false
/follow-redirects@1.15.6(debug@4.3.4):
@ -7180,6 +7226,12 @@ packages:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
/magic-string@0.30.9:
resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
/make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
@ -7848,7 +7900,7 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/pinia@2.1.7(typescript@5.4.5)(vue@3.4.21):
/pinia@2.1.7(typescript@5.4.5)(vue@3.4.23):
resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==}
peerDependencies:
'@vue/composition-api': ^1.4.0
@ -7862,8 +7914,8 @@ packages:
dependencies:
'@vue/devtools-api': 6.5.0
typescript: 5.4.5
vue: 3.4.21(typescript@5.4.5)
vue-demi: 0.14.6(vue@3.4.21)
vue: 3.4.23(typescript@5.4.5)
vue-demi: 0.14.6(vue@3.4.23)
dev: false
/pkg-dir@4.2.0:
@ -9834,13 +9886,13 @@ packages:
- supports-color
dev: true
/vite-svg-loader@5.1.0(vue@3.4.21):
/vite-svg-loader@5.1.0(vue@3.4.23):
resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==}
peerDependencies:
vue: '>=3.2.13'
dependencies:
svgo: 3.0.2
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: true
/vite@5.2.8(@types/node@20.12.7)(sass@1.74.1)(terser@5.24.0):
@ -9938,7 +9990,7 @@ packages:
- terser
dev: true
/vue-advanced-cropper@2.8.8(vue@3.4.21):
/vue-advanced-cropper@2.8.8(vue@3.4.23):
resolution: {integrity: sha512-yDM7Jb/gnxcs//JdbOogBUoHr1bhCQSto7/ohgETKAe4wvRpmqIkKSppMm1huVQr+GP1YoVlX/fkjKxvYzwwDQ==}
engines: {node: '>=8', npm: '>=5'}
peerDependencies:
@ -9947,14 +9999,14 @@ packages:
classnames: 2.3.1
debounce: 1.2.1
easy-bem: 1.1.1
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/vue-component-type-helpers@2.0.6:
resolution: {integrity: sha512-qdGXCtoBrwqk1BT6r2+1Wcvl583ZVkuSZ3or7Y1O2w5AvWtlvvxwjGhmz5DdPJS9xqRdDlgTJ/38ehWnEi0tFA==}
dev: true
/vue-demi@0.14.6(vue@3.4.21):
/vue-demi@0.14.6(vue@3.4.23):
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
engines: {node: '>=12'}
hasBin: true
@ -9966,10 +10018,10 @@ packages:
'@vue/composition-api':
optional: true
dependencies:
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/vue-demi@0.14.7(vue@3.4.21):
/vue-demi@0.14.7(vue@3.4.23):
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'}
hasBin: true
@ -9981,7 +10033,7 @@ packages:
'@vue/composition-api':
optional: true
dependencies:
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/vue-eslint-parser@9.4.2(eslint@8.57.0):
@ -10002,17 +10054,17 @@ packages:
- supports-color
dev: true
/vue-flatpickr-component@11.0.5(vue@3.4.21):
/vue-flatpickr-component@11.0.5(vue@3.4.23):
resolution: {integrity: sha512-Vfwg5uVU+sanKkkLzUGC5BUlWd5wlqAMq/UpQ6lI2BCZq0DDrXhOMX7hrevt8bEgglIq2QUv0K2Nl84Me/VnlA==}
engines: {node: '>=14.13.0'}
peerDependencies:
vue: ^3.2.0
dependencies:
flatpickr: 4.6.13
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/vue-i18n@9.11.1(vue@3.4.21):
/vue-i18n@9.11.1(vue@3.4.23):
resolution: {integrity: sha512-S7Xi8DkLQG4xnnbxkxzipJK6CdfLdZkmApn95st89HFGp8LTmTH0Tv+Zw6puhOCZJCFrH73PHo3Ylwd2+Bmdxg==}
engines: {node: '>= 16'}
peerDependencies:
@ -10021,24 +10073,24 @@ packages:
'@intlify/core-base': 9.11.1
'@intlify/shared': 9.11.1
'@vue/devtools-api': 6.6.1
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/vue-resize@2.0.0-alpha.1(vue@3.4.21):
/vue-resize@2.0.0-alpha.1(vue@3.4.23):
resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==}
peerDependencies:
vue: ^3.0.0
dependencies:
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/vue-router@4.3.0(vue@3.4.21):
/vue-router@4.3.0(vue@3.4.23):
resolution: {integrity: sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==}
peerDependencies:
vue: ^3.2.0
dependencies:
'@vue/devtools-api': 6.6.1
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false
/vue-template-compiler@2.7.14:
@ -10060,22 +10112,22 @@ packages:
typescript: 5.4.5
dev: true
/vue@3.4.21(typescript@5.4.5):
resolution: {integrity: sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==}
/vue@3.4.23(typescript@5.4.5):
resolution: {integrity: sha512-X1y6yyGJ28LMUBJ0k/qIeKHstGd+BlWQEOT40x3auJFTmpIhpbKLgN7EFsqalnJXq1Km5ybDEsp6BhuWKciUDg==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@vue/compiler-dom': 3.4.21
'@vue/compiler-sfc': 3.4.21
'@vue/runtime-dom': 3.4.21
'@vue/server-renderer': 3.4.21(vue@3.4.21)
'@vue/shared': 3.4.21
'@vue/compiler-dom': 3.4.23
'@vue/compiler-sfc': 3.4.23
'@vue/runtime-dom': 3.4.23
'@vue/server-renderer': 3.4.23(vue@3.4.23)
'@vue/shared': 3.4.23
typescript: 5.4.5
/vuemoji-picker@0.2.1(vue@3.4.21):
/vuemoji-picker@0.2.1(vue@3.4.23):
resolution: {integrity: sha512-wKRZBZclTdnQIT4jPzmkJ5Ci9ObzMFPjkuYb+/+/9h+mAZIUwdcPqYbEJCohbxJPoOvkuPVDeuOdTKR8hqqVLA==}
peerDependencies:
'@vue/composition-api': ^1.7.0
@ -10085,8 +10137,8 @@ packages:
optional: true
dependencies:
emoji-picker-element: 1.21.1
vue: 3.4.21(typescript@5.4.5)
vue-demi: 0.14.7(vue@3.4.21)
vue: 3.4.23(typescript@5.4.5)
vue-demi: 0.14.7(vue@3.4.23)
dev: false
/w3c-keyname@2.2.6:
@ -10526,11 +10578,11 @@ packages:
engines: {node: '>=12.20'}
dev: true
/zhyswan-vuedraggable@4.1.3(vue@3.4.21):
/zhyswan-vuedraggable@4.1.3(vue@3.4.23):
resolution: {integrity: sha512-q4Mp52tQIvTAWG0CKxLCVLyG/3RnIskDxoJvfjDZ2kM8yTcMkY80VTc8rd3q9KwqJ0UVtjEGLufb23sjDp0peQ==}
peerDependencies:
vue: ^3.0.1
dependencies:
sortablejs: 1.14.0
vue: 3.4.21(typescript@5.4.5)
vue: 3.4.23(typescript@5.4.5)
dev: false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 218 KiB

View File

@ -162,7 +162,7 @@ projectStore.loadAllProjects()
.app-content {
z-index: 10;
position: relative;
padding: 1.5rem 0.5rem 1rem;
padding: 1.5rem 0.5rem 0;
// TODO refactor: DRY `transition-timing-function` with `./navigation.vue`.
transition: margin-left $transition-duration;
@ -172,7 +172,7 @@ projectStore.loadAllProjects()
}
@media screen and (min-width: $tablet) {
padding: $navbar-height + 1.5rem 1.5rem 1rem 1.5rem;
padding: $navbar-height + 1.5rem 1.5rem 0 1.5rem;
}
&.is-menu-enabled {

View File

@ -338,10 +338,12 @@ const editor = useEditor({
HardBreak.extend({
addKeyboardShortcuts() {
return {
'Shift-Enter': () => this.editor.commands.setHardBreak(),
'Mod-Enter': () => {
if (contentHasChanged.value) {
bubbleSave()
}
return true
},
}
},
@ -891,8 +893,14 @@ ul[data-type='taskList'] {
padding: 0;
margin-left: 0;
li[data-checked='true'] {
color: var(--grey-500);
text-decoration: line-through;
}
li {
display: flex;
margin-top: 0.25rem;
> label {
flex: 0 0 auto;

View File

@ -88,7 +88,7 @@ const currentProject = computed<IProject>(() => {
})
useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value) : '')
const views = computed(() => currentProject.value?.views)
const views = computed(() => projectStore.projects[projectId]?.views)
// watchEffect would be called every time the prop would get a value assigned, even if that value was the same as before.
// This resulted in loading and setting the project multiple times, even when navigating away from it.
@ -161,6 +161,7 @@ function getViewTitle(view: IProjectView) {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
@media screen and (max-width: $tablet) {
justify-content: center;

View File

@ -30,45 +30,27 @@ import {computed, ref, watch} from 'vue'
import Filters from '@/components/project/partials/filters.vue'
import {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
import {useRouteQuery} from '@vueuse/router'
import {type TaskFilterParams} from '@/services/taskCollection'
const modelValue = defineModel<TaskFilterParams>({})
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = ref<TaskFilterParams>({})
const filter = useRouteQuery('filter')
watch(
() => modelValue.value,
() => props.modelValue,
(modelValue: TaskFilterParams) => {
value.value = modelValue
if (value.value.filter !== '' && value.value.filter !== getDefaultTaskFilterParams().filter) {
filter.value = value.value.filter
}
},
{immediate: true},
)
watch(
() => filter.value,
val => {
if (modelValue.value?.filter === val || typeof val === 'undefined') {
return
}
modelValue.value.filter = val
},
{immediate: true},
)
function emitChanges(newValue: TaskFilterParams) {
filter.value = newValue.filter
if (modelValue.value?.filter === newValue.filter && modelValue.value?.s === newValue.s) {
return
}
modelValue.value.filter = newValue.filter
modelValue.value.s = newValue.s
emit('update:modelValue', {
...value.value,
filter: newValue.filter,
s: newValue.s,
})
}
const hasFilters = computed(() => {

View File

@ -5,7 +5,7 @@
role="search"
>
<FilterInput
v-model="params.filter"
v-model="filterQuery"
:project-id="projectId"
@blur="change()"
/>
@ -28,7 +28,7 @@
<x-button
variant="secondary"
class="mr-2"
:disabled="params.filter === ''"
:disabled="filterQuery === ''"
@click.prevent.stop="clearFiltersAndEmit"
>
{{ $t('filters.clear') }}
@ -87,6 +87,16 @@ const params = ref<TaskFilterParams>({
s: '',
})
const filterQuery = ref('')
watch(
() => [params.value.filter, params.value.s],
() => {
const filter = params.value.filter || ''
const s = params.value.s || ''
filterQuery.value = filter || s
},
)
// Using watchDebounced to prevent the filter re-triggering itself.
watch(
() => modelValue,
@ -107,7 +117,7 @@ const projectStore = useProjectStore()
function change() {
const filter = transformFilterStringForApi(
params.value.filter,
filterQuery.value,
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
projectTitle => {
const found = projectStore.findProjectByExactname(projectTitle)
@ -142,7 +152,7 @@ function changeAndEmitButton() {
}
function clearFiltersAndEmit() {
params.value.filter = ''
filterQuery.value = ''
changeAndEmitButton()
}
</script>

View File

@ -773,11 +773,6 @@ $crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 1rem - 1.5rem - 11px';
$crazy-height-calculation-tasks: '#{$crazy-height-calculation} - 1rem - 2.5rem - 2rem - #{$button-height} - 1rem';
$filter-container-height: '1rem - #{$switch-view-height}';
// FIXME:
.app-content.project\.kanban, .app-content.task\.detail {
padding-bottom: 0 !important;
}
.kanban {
overflow-x: auto;
overflow-y: hidden;
@ -785,6 +780,10 @@ $filter-container-height: '1rem - #{$switch-view-height}';
margin: 0 -1.5rem;
padding: 0 1.5rem;
&:focus, .bucket .tasks:focus {
box-shadow: none;
}
@media screen and (max-width: $tablet) {
height: calc(#{$crazy-height-calculation} - #{$filter-container-height});
scroll-snap-type: x mandatory;

View File

@ -1,5 +1,5 @@
import {ref, shallowReactive, watch, computed, type ComputedGetter} from 'vue'
import {useRoute} from 'vue-router'
import {useRoute, useRouter} from 'vue-router'
import {useRouteQuery} from '@vueuse/router'
import TaskCollectionService, {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
@ -66,7 +66,6 @@ export function useTaskList(
const params = ref<TaskFilterParams>({...getDefaultTaskFilterParams()})
const search = ref('')
const page = useRouteQuery('page', '1', { transform: Number })
const sortBy = ref({ ...sortByDefault })
@ -74,10 +73,6 @@ export function useTaskList(
const allParams = computed(() => {
const loadParams = {...params.value}
if (search.value !== '') {
loadParams.s = search.value
}
return formatSortOrder(sortBy.value, loadParams)
})
@ -122,16 +117,38 @@ export function useTaskList(
const route = useRoute()
watch(() => route.query, (query) => {
const { page: pageQueryValue, search: searchQuery } = query
if (searchQuery !== undefined) {
search.value = searchQuery as string
const {
page: pageQueryValue,
s,
filter,
} = query
if (s !== undefined) {
params.value.s = s as string
}
if (pageQueryValue !== undefined) {
page.value = Number(pageQueryValue)
}
if (filter !== undefined) {
params.value.filter = filter
}
}, { immediate: true })
const router = useRouter()
watch(
() => [page.value, params.value.filter, params.value.s],
() => {
router.replace({
name: route.name,
params: route.params,
query: {
page: page.value,
filter: params.value.filter || undefined,
s: params.value.s || undefined,
},
})
},
{ deep: true },
)
// Only listen for query path changes
watch(() => JSON.stringify(getAllTasksParams.value), (newParams, oldParams) => {
@ -148,7 +165,6 @@ export function useTaskList(
totalPages,
currentPage: page,
loadTasks,
searchTerm: search,
params,
sortByParam: sortBy,
}

View File

@ -19,6 +19,13 @@ export function secondsToPeriod(seconds: number): { unit: PeriodUnit, amount: nu
}
}
if (seconds % SECONDS_A_MINUTE === 0) {
return {
unit: 'minutes',
amount: seconds / SECONDS_A_MINUTE,
}
}
return {
unit: 'hours',
amount: seconds / SECONDS_A_HOUR,

View File

@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import type { RouteLocation } from 'vue-router'
import {saveLastVisited} from '@/helpers/saveLastVisited'
import {saveProjectView, getProjectViewId} from '@/helpers/projectView'
import {getProjectViewId} from '@/helpers/projectView'
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
import {getNextWeekDate} from '@/helpers/time/getNextWeekDate'
import {LINK_SHARE_HASH_PREFIX} from '@/constants/linkShareHash'
@ -366,7 +366,6 @@ const router = createRouter({
path: '/projects/:projectId/:viewId',
name: 'project.view',
component: ProjectView,
beforeEnter: (to) => saveProjectView(parseInt(to.params.projectId as string), parseInt(to.params.viewId as string)),
props: route => ({
projectId: parseInt(route.params.projectId as string),
viewId: route.params.viewId ? parseInt(route.params.viewId as string): undefined,

View File

@ -207,13 +207,17 @@ export const useProjectStore = defineStore('project', () => {
}
function getAncestors(project: IProject): IProject[] {
if (typeof project === 'undefined') {
return []
}
if (!project?.parentProjectId) {
return [project]
}
const parentProject = projects.value[project.parentProjectId]
return [
...getAncestors(parentProject),
...(parentProject ? getAncestors(parentProject) : []),
project,
]
}

View File

@ -2,6 +2,7 @@
import {computed, watch} from 'vue'
import {useProjectStore} from '@/stores/projects'
import {useRoute, useRouter} from 'vue-router'
import {saveProjectView} from '@/helpers/projectView'
import ProjectList from '@/components/project/views/ProjectList.vue'
import ProjectGantt from '@/components/project/views/ProjectGantt.vue'
@ -53,6 +54,13 @@ watch(
redirectToFirstViewIfNecessary,
)
// using a watcher instead of beforeEnter because beforeEnter is not called when only the viewId changes
watch(
() => [projectId, viewId],
() => saveProjectView(projectId, viewId),
{immediate: true},
)
const route = useRoute()
</script>

View File

@ -17,11 +17,11 @@ import postcssEasings from 'postcss-easings'
import postcssEasingGradients from 'postcss-easing-gradients'
const pathSrc = fileURLToPath(new URL('./src', import.meta.url))
const pathSrc = fileURLToPath(new URL('./src', import.meta.url)).replaceAll('\\', '/')
// the @use rules have to be the first in the compiled stylesheets
const PREFIXED_SCSS_STYLES = `@use "sass:math";
@import "${pathSrc}/styles/common-imports";`
@import "${pathSrc}/styles/common-imports.scss";`
const isModernBuild = Boolean(process.env.BUILD_MODERN_ONLY)
const legacy = isModernBuild

6
go.mod
View File

@ -20,8 +20,8 @@ require (
code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3
dario.cat/mergo v1.0.0
github.com/ThreeDotsLabs/watermill v1.3.5
github.com/adlio/trello v1.11.0
github.com/arran4/golang-ical v0.2.7
github.com/adlio/trello v1.12.0
github.com/arran4/golang-ical v0.2.8
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/bbrks/go-blurhash v1.1.1
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
@ -190,8 +190,6 @@ require (
replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
replace github.com/adlio/trello v1.11.0 => github.com/kolaente/trello v1.8.1-0.20240410214605-9314fa638eab // https://github.com/adlio/trello/pull/95
go 1.21
toolchain go1.21.2

8
go.sum
View File

@ -24,13 +24,15 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg=
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
github.com/adlio/trello v1.12.0 h1:JqOE2GFHQ9YtEviRRRSnicSxPbt4WFOxhqXzjMOw8lw=
github.com/adlio/trello v1.12.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/arran4/golang-ical v0.2.7 h1:VO7YlVaGupZE15aj6NhUhte/MIfZuoIzkoI71VsG6Gg=
github.com/arran4/golang-ical v0.2.7/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/arran4/golang-ical v0.2.8 h1:8lsFcfQqzg0gBpIxq7fWr4RV+8SVENLMXpSic5xsFUs=
github.com/arran4/golang-ical v0.2.8/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@ -303,8 +305,6 @@ github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible h1:q7DbyV+sFjEoTuuUdRDNl2nlyfztkZgxVVCV7JhzIkY=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
github.com/kolaente/trello v1.8.1-0.20240410214605-9314fa638eab h1:KAEuOtvjNohQzONw0NpOlR4TlWgsPawY/PLyOiMHlFw=
github.com/kolaente/trello v1.8.1-0.20240410214605-9314fa638eab/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=

View File

@ -32,9 +32,9 @@ The to-do app to organize your life.
Also one of the two wild South American camelids which live in the high
alpine areas of the Andes and a relative of the llama.
Vikunja is a self-hosted To-Do list application with a web app and mobile apps for all platforms. It is licensed under the GPLv3.
Vikunja is a self-hosted To-Do list application with a web app and mobile apps for all platforms. It is licensed under the AGPLv3.
Find more info at vikunja.io.`,
Find out more at vikunja.io.`,
PreRun: webCmd.PreRun,
Run: webCmd.Run,
}

View File

@ -82,7 +82,6 @@ func init() {
if config.DatabaseType.GetString() == "sqlite" {
_, err = tx.Exec(`
create table buckets_dg_tmp
(
id INTEGER not null
@ -107,7 +106,7 @@ drop table buckets;
alter table buckets_dg_tmp
rename to buckets;
create unique index UQE_buckets_id
create unique index if not exists UQE_buckets_id
on buckets (id);
`)
return err

View File

@ -155,6 +155,21 @@ func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (task
projectIDs = append(projectIDs, p.ID)
}
views := map[int64]*ProjectView{}
err = s.In("project_id", projectIDs).Find(&views)
if err != nil {
return
}
viewIDs := []int64{}
for _, v := range views {
if projectsMap[v.ProjectID].Views == nil {
projectsMap[v.ProjectID].Views = []*ProjectView{}
}
projectsMap[v.ProjectID].Views = append(projectsMap[v.ProjectID].Views, v)
viewIDs = append(viewIDs, v.ID)
}
tasks, _, _, err := getTasksForProjects(s, rawProjects, u, &taskSearchOptions{
page: 0,
perPage: -1,
@ -194,17 +209,75 @@ func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (task
}
buckets := []*Bucket{}
err = s.In("project_id", projectIDs).Find(&buckets)
err = s.In("project_view_id", viewIDs).Find(&buckets)
if err != nil {
return
}
bucketIDs := []int64{}
for _, b := range buckets {
if _, exists := projectsMap[b.ProjectID]; !exists {
log.Debugf("[User Data Export] Project %d does not exist for bucket %d, omitting", b.ProjectID, b.ID)
view, exists := views[b.ProjectViewID]
if !exists {
log.Debugf("[User Data Export] Project view %d does not exist for bucket %d, omitting", b.ProjectViewID, b.ID)
continue
}
projectsMap[b.ProjectID].Buckets = append(projectsMap[b.ProjectID].Buckets, b)
_, exists = projectsMap[view.ProjectID]
if !exists {
log.Debugf("[User Data Export] Project %d does not exist for bucket %d, omitting", view.ProjectID, b.ID)
continue
}
projectsMap[view.ProjectID].Buckets = append(projectsMap[view.ProjectID].Buckets, b)
bucketIDs = append(bucketIDs, b.ID)
}
taskBuckets := []*TaskBucket{}
err = s.In("bucket_id", bucketIDs).Find(&taskBuckets)
if err != nil {
return
}
for _, tb := range taskBuckets {
view, exists := views[tb.ProjectViewID]
if !exists {
log.Debugf("[User Data Export] Project view %d does not exist, omitting", tb.ProjectViewID)
continue
}
_, exists = projectsMap[view.ProjectID]
if !exists {
log.Debugf("[User Data Export] Project %d does not exist, omitting", view.ProjectID)
continue
}
if projectsMap[view.ProjectID].TaskBuckets == nil {
projectsMap[view.ProjectID].TaskBuckets = []*TaskBucket{}
}
projectsMap[view.ProjectID].TaskBuckets = append(projectsMap[view.ProjectID].TaskBuckets, tb)
}
taskPositions := []*TaskPosition{}
err = s.In("project_view_id", viewIDs).Find(&taskPositions)
if err != nil {
return
}
for _, p := range taskPositions {
view, exists := views[p.ProjectViewID]
if !exists {
log.Debugf("[User Data Export] Project view %d does not exist, omitting", p.ProjectViewID)
continue
}
_, exists = projectsMap[view.ProjectID]
if !exists {
log.Debugf("[User Data Export] Project %d does not exist, omitting", view.ProjectID)
continue
}
if projectsMap[view.ProjectID].Positions == nil {
projectsMap[view.ProjectID].Positions = []*TaskPosition{}
}
projectsMap[view.ProjectID].Positions = append(projectsMap[view.ProjectID].Positions, p)
}
data, err := json.Marshal(projects)

View File

@ -529,7 +529,18 @@ func (l *AddTaskToTypesense) Handle(msg *message.Message) (err error) {
s := db.NewSession()
defer s.Close()
ttask, err := getTypesenseTaskForTask(s, event.Task, nil)
positionsMap, err := getPositionsForTask(s, event)
if err != nil {
return err
}
bucketsMap, err := getBucketsForTask(s, event)
if err != nil {
return err
}
ttask, err := getTypesenseTaskForTask(s, event.Task, nil, positionsMap, bucketsMap)
if err != nil {
return err
}
@ -540,6 +551,36 @@ func (l *AddTaskToTypesense) Handle(msg *message.Message) (err error) {
return
}
func getPositionsForTask(s *xorm.Session, event *TaskCreatedEvent) (positionsMap map[int64][]*TaskPositionWithView, err error) {
positions := []*TaskPositionWithView{}
err = s.
Table("project_views").
Where("project_views.project_id = ?", event.Task.ProjectID).
Join("LEFT", "task_positions", "project_views.id = task_positions.project_view_id AND task_positions.task_id = ?", event.Task.ID).
Find(&positions)
if err != nil {
return
}
positionsMap = make(map[int64][]*TaskPositionWithView, 1)
positionsMap[event.Task.ID] = positions
return
}
func getBucketsForTask(s *xorm.Session, event *TaskCreatedEvent) (bucketsMap map[int64][]*TaskBucket, err error) {
buckets := []*TaskBucket{}
err = s.
Where("task_id = ?", event.Task.ID).
Find(&buckets)
if err != nil {
return
}
bucketsMap = make(map[int64][]*TaskBucket, 1)
bucketsMap[event.Task.ID] = buckets
return
}
// UpdateTaskInTypesense represents a listener
type UpdateTaskInTypesense struct {
}

View File

@ -93,8 +93,10 @@ type ProjectWithTasksAndBuckets struct {
// An array of tasks which belong to the project.
Tasks []*TaskWithComments `xorm:"-" json:"tasks"`
// Only used for migration.
Buckets []*Bucket `xorm:"-" json:"buckets"`
BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
Buckets []*Bucket `xorm:"-" json:"buckets"`
TaskBuckets []*TaskBucket `xorm:"-" json:"task_buckets"`
Positions []*TaskPosition `xorm:"-" json:"positions"`
BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
}
// TableName returns a better name for the projects table
@ -110,15 +112,43 @@ type ProjectBackgroundType struct {
// ProjectBackgroundUpload represents the project upload background type
const ProjectBackgroundUpload string = "upload"
const FavoritesPseudoProjectID = -1
// FavoritesPseudoProject holds all tasks marked as favorites
var FavoritesPseudoProject = Project{
ID: -1,
ID: FavoritesPseudoProjectID,
Title: "Favorites",
Description: "This project has all tasks marked as favorites.",
IsFavorite: true,
Position: -1,
Created: time.Now(),
Updated: time.Now(),
Views: []*ProjectView{
{
ID: -1,
ProjectID: FavoritesPseudoProjectID,
Title: "List",
ViewKind: ProjectViewKindList,
Position: 100,
Filter: "done = false",
},
{
ID: -2,
ProjectID: FavoritesPseudoProjectID,
Title: "Gantt",
ViewKind: ProjectViewKindGantt,
Position: 200,
},
{
ID: -3,
ProjectID: FavoritesPseudoProjectID,
Title: "Table",
ViewKind: ProjectViewKindTable,
Position: 300,
},
},
Created: time.Now(),
Updated: time.Now(),
}
// ReadAll gets all projects a user has access to
@ -146,6 +176,9 @@ func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
}
projects := []*Project{project}
err = addProjectDetails(s, projects, a)
if err == nil && len(projects) > 0 {
projects[0].ParentProjectID = 0
}
return projects, 0, 0, err
}
@ -207,6 +240,7 @@ func (p *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int,
func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
if p.ID == FavoritesPseudoProject.ID {
p.Views = FavoritesPseudoProject.Views
// Already "built" the project in CanRead
return nil
}
@ -226,6 +260,11 @@ func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
p.OwnerID = sf.OwnerID
}
_, isShareAuth := a.(*LinkSharing)
if isShareAuth {
p.ParentProjectID = 0
}
// Get project owner
p.Owner, err = user.GetUserByID(s, p.OwnerID)
if err != nil {
@ -1073,6 +1112,46 @@ func (p *Project) Delete(s *xorm.Session, a web.Auth) (err error) {
}
}
// Delete related project entities
views, err := getViewsForProject(s, p.ID)
if err != nil {
return
}
viewIDs := []int64{}
for _, v := range views {
viewIDs = append(viewIDs, v.ID)
}
_, err = s.In("project_view_id", viewIDs).Delete(&Bucket{})
if err != nil {
return
}
_, err = s.In("id", viewIDs).Delete(&ProjectView{})
if err != nil {
return
}
err = removeFromFavorite(s, p.ID, a, FavoriteKindProject)
if err != nil {
return
}
_, err = s.Where("project_id = ?", p.ID).Delete(&LinkSharing{})
if err != nil {
return
}
_, err = s.Where("project_id = ?", p.ID).Delete(&ProjectUser{})
if err != nil {
return
}
_, err = s.Where("project_id = ?", p.ID).Delete(&TeamProject{})
if err != nil {
return
}
// Delete the project
_, err = s.ID(p.ID).Delete(&Project{})
if err != nil {

View File

@ -159,6 +159,7 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
log.Debugf("Duplicated all link shares from project %d into %d", pd.ProjectID, pd.Project.ID)
err = pd.Project.ReadOne(s, doer)
return
}
@ -226,9 +227,11 @@ func duplicateViews(s *xorm.Session, pd *ProjectDuplicate, doer web.Auth, taskMa
})
}
_, err = s.Insert(&taskBuckets)
if err != nil {
return err
if len(taskBuckets) > 0 {
_, err = s.Insert(&taskBuckets)
if err != nil {
return err
}
}
oldTaskPositions := []*TaskPosition{}
@ -246,7 +249,9 @@ func duplicateViews(s *xorm.Session, pd *ProjectDuplicate, doer web.Auth, taskMa
})
}
_, err = s.Insert(&taskPositions)
if len(taskPositions) > 0 {
_, err = s.Insert(&taskPositions)
}
return
}

View File

@ -347,10 +347,22 @@ func (p *ProjectView) Update(s *xorm.Session, _ web.Auth) (err error) {
return
}
func GetProjectViewByIDAndProject(s *xorm.Session, id, projectID int64) (view *ProjectView, err error) {
func GetProjectViewByIDAndProject(s *xorm.Session, viewID, projectID int64) (view *ProjectView, err error) {
if projectID == FavoritesPseudoProjectID && viewID < 0 {
for _, v := range FavoritesPseudoProject.Views {
if v.ID == viewID {
return v, nil
}
}
return nil, &ErrProjectViewDoesNotExist{
ProjectViewID: viewID,
}
}
view = &ProjectView{}
exists, err := s.
Where("id = ? AND project_id = ?", id, projectID).
Where("id = ? AND project_id = ?", viewID, projectID).
NoAutoCondition().
Get(view)
if err != nil {
@ -359,7 +371,7 @@ func GetProjectViewByIDAndProject(s *xorm.Session, id, projectID int64) (view *P
if !exists {
return nil, &ErrProjectViewDoesNotExist{
ProjectViewID: id,
ProjectViewID: viewID,
}
}

View File

@ -100,6 +100,10 @@ func getTaskFilterOptsFromCollection(tf *TaskCollection, projectView *ProjectVie
param.orderBy = getSortOrderFromString(tf.OrderBy[i])
}
if s == taskPropertyPosition && projectView != nil && projectView.ID < 0 {
continue
}
if s == taskPropertyPosition && projectView != nil {
param.projectViewID = projectView.ID
}
@ -249,11 +253,20 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
opts.perPage = perPage
if view != nil {
opts.sortby = append(opts.sortby, &sortParam{
projectViewID: view.ID,
sortBy: taskPropertyPosition,
orderBy: orderAscending,
})
var hasOrderByPosition bool
for _, param := range opts.sortby {
if param.sortBy == taskPropertyPosition {
hasOrderByPosition = true
break
}
}
if !hasOrderByPosition {
opts.sortby = append(opts.sortby, &sortParam{
projectViewID: view.ID,
sortBy: taskPropertyPosition,
orderBy: orderAscending,
})
}
}
shareAuth, is := a.(*LinkSharing)

View File

@ -65,6 +65,14 @@ func (tc *TaskComment) TableName() string {
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/comments [put]
func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) {
tc.Created = time.Time{}
tc.Updated = time.Time{}
return tc.CreateWithTimestamps(s, a)
}
func (tc *TaskComment) CreateWithTimestamps(s *xorm.Session, a web.Auth) (err error) {
// Check if the task exists
task, err := GetTaskSimple(s, &Task{ID: tc.TaskID})
if err != nil {
@ -77,9 +85,16 @@ func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) {
}
tc.AuthorID = tc.Author.ID
_, err = s.Insert(tc)
if err != nil {
return
if !tc.Created.IsZero() && !tc.Updated.IsZero() {
_, err = s.NoAutoTime().Insert(tc)
if err != nil {
return
}
} else {
_, err = s.Insert(tc)
if err != nil {
return
}
}
return events.Dispatch(&TaskCommentCreatedEvent{
@ -255,7 +270,7 @@ func (tc *TaskComment) ReadAll(s *xorm.Session, auth web.Auth, search string, pa
query := s.
Where(builder.And(where...)).
Join("LEFT", "users", "users.id = task_comments.author_id").
OrderBy("task_comments.id asc")
OrderBy("task_comments.created asc")
if limit > 0 {
query = query.Limit(limit, start)
}

View File

@ -297,6 +297,7 @@ func (d *dbTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task, totalCo
queryCount = queryCount.Join("LEFT", "task_buckets", "task_buckets.task_id = tasks.id")
}
totalCount, err = queryCount.
Select("count(DISTINCT tasks.id)").
Count(&Task{})
return
}
@ -379,6 +380,10 @@ func convertParsedFilterToTypesense(rawFilters []*taskFilter) (filterBy string,
f.field = "project_id"
}
if f.field == "bucket_id" {
f.field = "buckets"
}
filter := f.field
switch f.comparator {
@ -450,6 +455,7 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
"(" + filter + ")",
}
var projectViewIDForPosition int64
var sortbyFields []string
for i, param := range opts.sortby {
// Validate the params
@ -457,17 +463,19 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
return nil, totalCount, err
}
sortBy := param.sortBy
// Typesense does not allow sorting by ID, so we sort by created timestamp instead
if param.sortBy == taskPropertyID {
param.sortBy = taskPropertyCreated
sortBy = taskPropertyCreated
}
if param.sortBy == taskPropertyPosition {
param.sortBy = "positions.view_" + strconv.FormatInt(param.projectViewID, 10)
continue
sortBy = "positions.view_" + strconv.FormatInt(param.projectViewID, 10)
projectViewIDForPosition = param.projectViewID
}
sortbyFields = append(sortbyFields, param.sortBy+"(missing_values:last):"+param.orderBy.String())
sortbyFields = append(sortbyFields, sortBy+"(missing_values:last):"+param.orderBy.String())
if i == 2 {
// Typesense supports up to 3 sorting parameters
@ -525,9 +533,14 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
return nil, 0, err
}
err = t.s.
query := t.s.
In("id", taskIDs).
OrderBy(orderby).
Find(&tasks)
OrderBy(orderby)
if projectViewIDForPosition != 0 {
query = query.Join("LEFT", "task_positions", "task_positions.task_id = tasks.id AND task_positions.project_view_id = ?", projectViewIDForPosition)
}
err = query.Find(&tasks)
return tasks, int64(*result.Found), err
}

View File

@ -1486,6 +1486,18 @@ func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) {
return
}
// Delete all positions
_, err = s.Where("task_id = ?", t.ID).Delete(&TaskPosition{})
if err != nil {
return
}
// Delete all bucket relations
_, err = s.Where("task_id = ?", t.ID).Delete(&TaskBucket{})
if err != nil {
return
}
// Actually delete the task
_, err = s.ID(t.ID).Delete(Task{})
if err != nil {

View File

@ -146,22 +146,10 @@ func CreateTypesenseCollections() error {
Name: "updated",
Type: "int64", // unix timestamp
},
{
Name: "bucket_id",
Type: "int64",
},
{
Name: "position",
Type: "float",
},
{
Name: "created_by_id",
Type: "int64",
},
{
Name: "project_view_id",
Type: "int64",
},
{
Name: "reminders",
Type: "object[]", // TODO
@ -192,6 +180,14 @@ func CreateTypesenseCollections() error {
Type: "object[]", // TODO
Optional: pointer.True(),
},
{
Name: "positions",
Type: "object",
},
{
Name: "buckets",
Type: "int64[]",
},
},
}
@ -248,14 +244,8 @@ func ReindexAllTasks() (err error) {
return
}
func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int64]*Project) (ttask *typesenseTask, err error) {
positions := []*TaskPosition{}
err = s.Where("task_id = ?", task.ID).Find(&positions)
if err != nil {
return
}
ttask = convertTaskToTypesenseTask(task, positions)
func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int64]*Project, taskPositionCache map[int64][]*TaskPositionWithView, taskBucketCache map[int64][]*TaskBucket) (ttask *typesenseTask, err error) {
ttask = convertTaskToTypesenseTask(task, taskPositionCache[task.ID], taskBucketCache[task.ID])
var p *Project
if projectsCache == nil {
@ -299,9 +289,19 @@ func reindexTasksInTypesense(s *xorm.Session, tasks map[int64]*Task) (err error)
projects := make(map[int64]*Project)
typesenseTasks := []interface{}{}
positionsByTask, err := getPositionsByTask(s)
if err != nil {
return err
}
bucketsByTask, err := getBucketsByTask(s)
if err != nil {
return err
}
for _, task := range tasks {
ttask, err := getTypesenseTaskForTask(s, task, projects)
ttask, err := getTypesenseTaskForTask(s, task, projects, positionsByTask, bucketsByTask)
if err != nil {
return err
}
@ -320,11 +320,55 @@ func reindexTasksInTypesense(s *xorm.Session, tasks map[int64]*Task) (err error)
return err
}
log.Debugf("Indexed tasks %v into Typesense", tasks)
log.Debugf("Indexed %d tasks into Typesense", len(tasks))
return nil
}
type TaskPositionWithView struct {
ProjectView `xorm:"extends"`
TaskPosition `xorm:"extends"`
}
func getPositionsByTask(s *xorm.Session) (positionsByTask map[int64][]*TaskPositionWithView, err error) {
rawPositions := []*TaskPositionWithView{}
err = s.
Table("project_views").
Join("LEFT", "task_positions", "project_views.id = task_positions.project_view_id").
Find(&rawPositions)
if err != nil {
return
}
positionsByTask = make(map[int64][]*TaskPositionWithView, len(rawPositions))
for _, p := range rawPositions {
_, has := positionsByTask[p.TaskID]
if !has {
positionsByTask[p.TaskID] = []*TaskPositionWithView{}
}
positionsByTask[p.TaskID] = append(positionsByTask[p.TaskID], p)
}
return positionsByTask, nil
}
func getBucketsByTask(s *xorm.Session) (positionsByTask map[int64][]*TaskBucket, err error) {
rawBuckets := []*TaskBucket{}
err = s.Find(&rawBuckets)
if err != nil {
return
}
positionsByTask = make(map[int64][]*TaskBucket, len(rawBuckets))
for _, p := range rawBuckets {
_, has := positionsByTask[p.TaskID]
if !has {
positionsByTask[p.TaskID] = []*TaskBucket{}
}
positionsByTask[p.TaskID] = append(positionsByTask[p.TaskID], p)
}
return positionsByTask, nil
}
func indexDummyTask() (err error) {
// The initial sync should contain one dummy task with all related fields populated so that typesense
// creates the indexes properly. A little hacky, but gets the job done.
@ -386,6 +430,13 @@ func indexDummyTask() (err error) {
},
},
},
Positions: map[string]float64{
"view_1": 10,
"view_2": 30,
"view_3": 5450,
"view_4": 42,
},
Buckets: []int64{42},
}
_, err = typesenseClient.Collection("tasks").
@ -430,9 +481,10 @@ type typesenseTask struct {
Attachments interface{} `json:"attachments"`
Comments interface{} `json:"comments"`
Positions map[string]float64 `json:"positions"`
Buckets []int64 `json:"buckets"`
}
func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesenseTask {
func convertTaskToTypesenseTask(task *Task, positions []*TaskPositionWithView, buckets []*TaskBucket) *typesenseTask {
tt := &typesenseTask{
ID: fmt.Sprintf("%d", task.ID),
@ -461,6 +513,8 @@ func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesens
Labels: task.Labels,
//RelatedTasks: task.RelatedTasks,
Attachments: task.Attachments,
Positions: make(map[string]float64, len(positions)),
Buckets: make([]int64, 0, len(buckets)),
}
if task.DoneAt.IsZero() {
@ -477,7 +531,15 @@ func convertTaskToTypesenseTask(task *Task, positions []*TaskPosition) *typesens
}
for _, position := range positions {
tt.Positions["view_"+strconv.FormatInt(position.ProjectViewID, 10)] = position.Position
pos := position.TaskPosition.Position
if pos == 0 {
pos = float64(task.ID)
}
tt.Positions["view_"+strconv.FormatInt(position.ProjectView.ID, 10)] = pos
}
for _, bucket := range buckets {
tt.Buckets = append(tt.Buckets, bucket.BucketID)
}
return tt

View File

@ -64,6 +64,11 @@ func insertFromStructure(s *xorm.Session, str []*models.ProjectWithTasksAndBucke
}
p.ID = 0
for _, view := range p.Views {
view.ProjectID = 0
}
err = createProject(s, p, &archivedProjects, labels, user)
if err != nil {
return err
@ -167,7 +172,7 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
}
// Create all buckets
buckets := make(map[int64]*models.Bucket) // old bucket id is the key
bucketsByOldID := make(map[int64]*models.Bucket) // old bucket id is the key
if len(project.Buckets) > 0 {
log.Debugf("[creating structure] Creating %d buckets", len(project.Buckets))
}
@ -179,40 +184,45 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
if err != nil {
return
}
buckets[oldID] = bucket
bucketsByOldID[oldID] = bucket
log.Debugf("[creating structure] Created bucket %d, old ID was %d", bucket.ID, oldID)
}
// Create all views, create default views if we don't have any
viewsByOldIDs := make(map[int64]*models.ProjectView, len(oldViews))
if len(oldViews) > 0 {
for _, view := range oldViews {
oldID := view.ID
view.ID = 0
if view.DefaultBucketID != 0 {
bucket, has := buckets[view.DefaultBucketID]
bucket, has := bucketsByOldID[view.DefaultBucketID]
if has {
view.DefaultBucketID = bucket.ID
}
}
if view.DoneBucketID != 0 {
bucket, has := buckets[view.DoneBucketID]
bucket, has := bucketsByOldID[view.DoneBucketID]
if has {
view.DoneBucketID = bucket.ID
}
}
view.ProjectID = project.ID
err = view.Create(s, user)
if err != nil {
return
}
viewsByOldIDs[oldID] = view
}
} else {
// Only using the default views
// Add all buckets to the default kanban view
for _, view := range project.Views {
if view.ViewKind == models.ProjectViewKindKanban {
for _, b := range buckets {
for _, b := range bucketsByOldID {
b.ProjectViewID = view.ID
err = b.Update(s, user)
if err != nil {
@ -227,7 +237,7 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
log.Debugf("[creating structure] Creating %d tasks", len(tasks))
setBucketOrDefault := func(task *models.Task) {
bucket, exists := buckets[task.BucketID]
bucket, exists := bucketsByOldID[task.BucketID]
if exists {
task.BucketID = bucket.ID
} else if task.BucketID > 0 {
@ -240,6 +250,7 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
}
tasksByOldID := make(map[int64]*models.TaskWithComments, len(tasks))
newTaskIDs := []int64{}
// Create all tasks
for i, t := range tasks {
setBucketOrDefault(&tasks[i].Task)
@ -251,6 +262,8 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
continue
}
newTaskIDs = append(newTaskIDs, t.ID)
if err != nil {
return
}
@ -359,10 +372,11 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
log.Debugf("[creating structure] Associated task %d with label %d", t.ID, lb.ID)
}
// Comments
for _, comment := range t.Comments {
comment.TaskID = t.ID
comment.ID = 0
err = comment.Create(s, user)
err = comment.CreateWithTimestamps(s, user)
if err != nil {
return
}
@ -400,6 +414,58 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
}
}
if len(viewsByOldIDs) > 0 {
newPositions := []*models.TaskPosition{}
for _, pos := range project.Positions {
_, hasTask := tasksByOldID[pos.TaskID]
_, hasView := viewsByOldIDs[pos.ProjectViewID]
if !hasTask || !hasView {
continue
}
newPositions = append(newPositions, &models.TaskPosition{
TaskID: tasksByOldID[pos.TaskID].ID,
ProjectViewID: viewsByOldIDs[pos.ProjectViewID].ID,
Position: pos.Position,
})
}
if len(newPositions) > 0 {
_, err = s.In("task_id", newTaskIDs).Delete(&models.TaskPosition{})
if err != nil {
return
}
_, err = s.Insert(newPositions)
if err != nil {
return
}
}
newTaskBuckets := make([]*models.TaskBucket, 0, len(project.TaskBuckets))
for _, tb := range project.TaskBuckets {
_, hasTask := tasksByOldID[tb.TaskID]
_, hasBucket := bucketsByOldID[tb.BucketID]
if !hasTask || !hasBucket {
continue
}
newTaskBuckets = append(newTaskBuckets, &models.TaskBucket{
TaskID: tasksByOldID[tb.TaskID].ID,
BucketID: bucketsByOldID[tb.BucketID].ID,
ProjectViewID: bucketsByOldID[tb.BucketID].ProjectViewID,
})
}
if len(newTaskBuckets) > 0 {
_, err = s.In("task_id", newTaskIDs).Delete(&models.TaskBucket{})
if err != nil {
return
}
_, err = s.Insert(newTaskBuckets)
if err != nil {
return
}
}
}
project.Tasks = tasks
project.Buckets = originalBuckets

View File

@ -156,10 +156,18 @@ func setupSentry(e *echo.Echo) {
if hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("url", c.Request().URL)
hub.CaptureException(herr.Internal)
if herr.Internal == nil {
hub.CaptureException(err)
} else {
hub.CaptureException(herr.Internal)
}
})
} else {
sentry.CaptureException(herr.Internal)
if herr.Internal == nil {
sentry.CaptureException(err)
} else {
sentry.CaptureException(herr.Internal)
}
log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
}
log.Debugf("Error '%s' sent to sentry", err.Error())