Compare commits

..
Author SHA1 Message Date
kolaente 4d684992c6 feat(navigation): use focus-visible for nav items 2024-09-19 12:35:08 +02:00
kolaente 4dd9d5de67 fix(filters): correctly transform and populate saved filter when creating and editing
This fixes a bug where the checkbox "include nulls" during creation was not checked but would be saved as such.
2024-09-19 12:21:19 +02:00
kolaente ec902b051b fix(task): mark related task as done from the task detail view
Resolves https://github.com/go-vikunja/vikunja/issues/326
2024-09-19 11:31:06 +02:00
kolaente 4b2b8e3b83 chore: rearrange cron registers 2024-09-19 11:24:43 +02:00
kolaente bc52da4029 fix(filter): make sure tasks are in a correct bucket and position when they are part of a date filter
Whenever a task is part of a date filter, it might fall in or out of a filter bucket without anything changing, other than the current time. For example, a filter condition like due_date > now may include different tasks depending on the current time.
For these kinds of tasks to properly show up in the kanban view of a filter, there has to be an entry in the task_buckets table. These entries only got updated when either a task was updated or the filter itself was updated. To account for th changing of time, we also need to check periodically if tasks are now part or not anymore part of that filter.
This change adds a cron task to do precisely that.
We'll have to see if this works resource-wise, but the cron is not the only one doing a bunch of sql queries so it might be fine after all.

Resolves https://community.vikunja.io/t/tasks-in-saved-filter-appear-in-list-view-but-are-not-visible-in-kanban-view/2800
2024-09-19 11:19:48 +02:00
kolaente 2123da49a3 fix(task): cleanup old task positions and task buckets when adding an updated or created task to filter 2024-09-19 09:30:21 +02:00
kolaente c53a761396 fix(task): add task to filter view after it was updated
Maybe resolves https://community.vikunja.io/t/tasks-in-saved-filter-appear-in-list-view-but-are-not-visible-in-kanban-view/2800
2024-09-19 09:30:20 +02:00
kolaente c84b50b3ee fix(labels): trigger task updated for bulk label task update 2024-09-19 08:59:34 +02:00
kolaente c5b82fc591 fix: test selector 2024-09-18 21:28:08 +02:00
renovateandkonrad 1f00180af6 fix(deps): update dependency vue to v3.5.6 2024-09-18 17:02:42 +00:00
renovateandkonrad 33e851f543 fix(deps): update dependency tailwindcss to v3.4.12 2024-09-18 17:02:06 +00:00
kolaente 16d7386975 fix(modal): make scrolling on iOS Safari work
This fixes a bug where the full-screen modal would not work on iOS Safari (and only there). It's unclear why this is happening due to a single overflow: visible statement though.

Resolves https://github.com/go-vikunja/vikunja/issues/325
2024-09-18 19:00:47 +02:00
kolaente 6d9bda8cc1 fix(projects): description not visible on mobile 2024-09-18 18:47:46 +02:00
kolaente abf92e29fa fix(task): dragging and dropping on mobile
This change fixes a regression introduced in 1cbb93ea9b.
In that change, the whole task area was made clickable using mouse events directly. Unfortunately, this also prevented the parent component of the task component to recieve them, essentially never getting notified about the mouse movement and thus never dragging the task. I don't know why this is only a problem on Safari, but it might be related to https://github.com/SortableJS/Sortable/issues/1571#issuecomment-535684451

Resolves https://community.vikunja.io/t/task-re-ordering-is-not-working-in-safari/1916
Resolves https://kolaente.dev/vikunja/vikunja/issues/2092
Resolves https://github.com/go-vikunja/vikunja/issues/304
2024-09-18 18:00:27 +02:00
kolaente f4d628550f chore(tasks): move drag options to direct attributes instead of v-bind 2024-09-18 16:34:24 +02:00
kolaente 1d352fcbf3 chore(devenv): do not install cypress on darwin 2024-09-18 14:30:38 +02:00
kolaente a1105434bf chore: remove console.log 2024-09-18 13:38:30 +02:00
kolaente acc7c9f8f5 fix(editor): restore the current value, not the one from a previous task
Resolves https://community.vikunja.io/t/task-description-is-overwritten-when-pressing-esc-key/2813
2024-09-18 13:36:56 +02:00
kolaente cc64ca6406 fix(task): align task title on mobile popup 2024-09-18 11:36:06 +02:00
kolaente 89f78cd369 chore: add go and direnv to recommended vscode extensions 2024-09-18 10:43:23 +02:00
renovateandkonrad 14c3b13823 fix(deps): update tiptap to v2.7.1 2024-09-18 08:33:10 +00:00
renovate c2a7438814 chore(deps): update dev-dependencies 2024-09-18 01:18:02 +00:00
renovateandkonrad 8fe37fd900 fix(deps): update tiptap to v2.7.0 2024-09-17 10:11:13 +00:00
renovate 88b4ab1768 fix(deps): update module github.com/prometheus/client_golang to v1.20.4 2024-09-17 09:15:34 +00:00
kolaente c252c8f0cd fix: reset id before creating 2024-09-17 10:46:14 +02:00
renovateandkonrad 410ff28b71 fix(deps): update vueuse to v11.1.0 2024-09-17 08:11:36 +00:00
renovateandkonrad d98dd7ecbb chore(deps): update dev-dependencies 2024-09-17 08:10:22 +00:00
renovate fc73085876 fix(deps): update dependency date-fns to v4 2024-09-17 05:16:09 +00:00
renovate c71f7334b4 chore(deps): update dev-dependencies 2024-09-15 10:15:16 +00:00
kolaente 6a5342bd49 fix(modal): do not prevent scrolling on mobile
Maybe related to https://github.com/go-vikunja/vikunja/issues/325
2024-09-15 11:38:17 +02:00
renovate b4ea1bb86a fix(deps): update dependency vue-router to v4.4.5 2024-09-14 11:15:19 +00:00
renovateandkonrad 378d0ae9bb fix(deps): update dependency vue to v3.5.5 2024-09-14 10:17:49 +00:00
kolaente 6f27e1401a fix(user): do not create user with existing id
Resolves https://vikunja.sentry.io/share/issue/6f1e37d4b8b248188e20650234a45cde/
2024-09-14 12:16:05 +02:00
kolaente bf5cafc03f fix: lint 2024-09-14 12:12:47 +02:00
kolaente f4a7326b68 fix(filter): do not replace labels keyword when the value is 'label'
Resolves https://community.vikunja.io/t/filtering-by-label-ux-issues/2393/16
2024-09-14 12:11:54 +02:00
kolaente 1451f6e46f fix(kanban): correctly paginate filtered kanban buckets
Resolves https://github.com/go-vikunja/vikunja/issues/314
2024-09-14 09:37:21 +02:00
Frederick [Bot]anddrone 427eb2a618 chore(i18n): update translations via Crowdin 2024-09-14 00:25:59 +00:00
kolaente e4b541e653 fix(view): correctly get paginated task results 2024-09-13 22:56:41 +02:00
kolaente 45ff5907e6 fix(view): correctly resolve bucket filter when paginating 2024-09-13 19:45:48 +02:00
kolaente 5a7c3927f3 fix(view): correctly resolve label for filtered views or buckets 2024-09-13 19:14:54 +02:00
kolaente f425d98b4d fix(filters): do not replace filter or project values when the id value resolves to undefined
This change fixes a bug where the label title in the query string would be replaced to undefined, resulting in an invalid filter. The underlying problem was the resolved filter query string got re-parsed and the id value of the labels were resolved to undefined (and rendered as that string) in the process.

Resolves https://community.vikunja.io/t/filtering-by-label-ux-issues/2393/14
2024-09-13 18:42:42 +02:00
kolaente 4c55016c1a fix(task): multiple overlapping defer due date popups
Resolves https://github.com/go-vikunja/vikunja/issues/131
2024-09-13 17:44:26 +02:00
kolaente 1a8f12ac13 fix(kanban): do not mark first bucked as done bucket in filter bucket mode
Resolves https://github.com/go-vikunja/vikunja/issues/313
2024-09-13 11:52:44 +02:00
kolaente 7b873ec31c fix(table): make sorting for two-word properties work
Resolves https://community.vikunja.io/t/various-sorting-filtering-issues/2781/8
2024-09-13 11:03:38 +02:00
kolaente 8b77832af2 fix(test): cypress test selector 2024-09-13 10:28:48 +02:00
Frederick [Bot]anddrone 14710d0bba chore(i18n): update translations via Crowdin 2024-09-13 00:25:12 +00:00
kolaente 945f25b818 fix(kanban): make kanban full width on mobile
Related to https://github.com/go-vikunja/vikunja/issues/309
2024-09-12 16:42:12 +02:00
kolaente f53b93d98a fix(modal): make sure modal and its content scrolls properly on mobile
Related to https://github.com/go-vikunja/vikunja/issues/309
2024-09-12 16:22:18 +02:00
kolaente 97e030a1fc fix(home): explicitly use filter for tasks on home page when one is set
Resolves https://github.com/go-vikunja/vikunja/issues/289
Resolves https://community.vikunja.io/t/various-sorting-filtering-issues/2781/5
2024-09-12 15:51:08 +02:00
renovateandkonrad b708db7025 chore(deps): update golangci/golangci-lint docker tag to v1.61.0 (#2678)
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-09-12 13:31:26 +00:00
kolaente 6bfe71b30e fix(filters): immediately propagate changes
Resolves https://community.vikunja.io/t/filtering-startdate-now-is-invalid/2613/6
2024-09-12 15:28:39 +02:00
renovateandkonrad db196bdffe fix(deps): update dependency @intlify/unplugin-vue-i18n to v5 2024-09-12 12:42:40 +00:00
renovateandkonrad 9428acd5e1 fix(deps): update dependency express to v4.21.0 2024-09-12 12:41:45 +00:00
renovateandkonrad 58a60fb4ae fix(deps): update dependency vue-router to v4.4.4 2024-09-12 12:36:18 +00:00
renovateandkonrad f4ca22a6ad fix(deps): update dependency flexsearch to v0.7.43 (#2095)
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-09-12 12:35:35 +00:00
kolaente 7e38a9f442 fix(kanban): make task creation loading spinner actually visible 2024-09-12 14:33:44 +02:00
renovateandkonrad a0a77818b1 fix(deps): update dependency vue-i18n to v10.0.1 2024-09-12 11:39:50 +00:00
kolaente bdccd633fe fix(labels): remove input interactivity when label edit is disabled 2024-09-12 13:39:03 +02:00
kolaente 1f3eb8f2a3 chore: fix comment 2024-09-12 13:39:03 +02:00
renovate 8891ef347e fix(deps): update dependency tailwindcss to v3.4.11 2024-09-12 10:26:57 +00:00
kolaente 3b8ccbad4f fix(multiselect): make selectPlaceholder optional 2024-09-12 11:29:39 +02:00
kolaente cc1b4bbd1b fix(task): make print styles work when printing task detail view from kanban
Resolves https://community.vikunja.io/t/feature-request-export-a-task-as-pdf/2735/6
2024-09-12 11:02:38 +02:00
Frederick [Bot]andkolaente 9dd95101f9 [skip ci] Updated swagger docs 2024-09-12 08:42:38 +00:00
kolaente 20724f6fb5 fix(task): cyclomatic complexity 2024-09-12 10:24:16 +02:00
Frederick [Bot]anddrone e1ea84fcd4 chore(i18n): update translations via Crowdin 2024-09-12 00:26:38 +00:00
kolaente 8d730543c3 fix(project): reset id before creating
Resolves https://vikunja.sentry.io/share/issue/6f1e37d4b8b248188e20650234a45cde/
2024-09-11 23:46:13 +02:00
renovateandkonrad a3d133bfee fix(deps): update dependency vue-i18n to v10 2024-09-11 18:01:57 +00:00
renovateandkonrad 7b37ad0594 fix(deps): update dependency @sentry/vue to v8.30.0 2024-09-11 17:36:28 +00:00
kolaente 4aeb228c3a fix(project): show description in title attribute without html
Resolves https://community.vikunja.io/t/feedback-to-vikunja-0-24-0/2557/6
2024-09-11 18:23:43 +02:00
kolaente b52d6dbf59 fix(projects): do not hide 6th project on project overview 2024-09-11 18:20:57 +02:00
kolaente 76f7797e56 chore: remove console.log 2024-09-11 18:14:10 +02:00
kolaente 55dd7d2981 fix(task): specify task index when creating multiple tasks at once
This change allows to specify the task index when creating a task, which will then be checked to avoid duplicates and used. This allows us to calculate the indexes for all tasks beforehand when creating them at once using quick add magic.
The method is not bulletproof, but already fixes a problem where multiple tasks would have the same index when created that way.

Resolves https://community.vikunja.io/t/add-multiple-tasks-at-once/333/16
2024-09-11 17:58:42 +02:00
kolaente 44a43b9f86 fix(auth): restrict max password length to 72 bytes
Bcrypt allows a maximum of 72 bytes. This is part of the algorithm and not something we could change in Vikunja. The solution here was to restrict the password during registration to a max length of 72 bytes. In the future, this should be changed to hash passwords with sha512 or similar before hashing them with bcrypt. Because they should also be salted in that case and the added complexity during the migration phase, this was not implemented yet.
The change in this commit only improves the error handling to return an input error instead of a server error when the user enters a password > 72 bytes.

Resolves https://vikunja.sentry.io/share/issue/e8e0b64612d84504942feee002ac498a/
2024-09-10 18:23:06 +02:00
kolaente eb95caf757 fix(password): validate password before sending request to api 2024-09-10 17:44:52 +02:00
kolaente 1085a6583b fix(caldav): reject invalid project id with error 400
Resolves https://vikunja.sentry.io/share/issue/6fc18edefa0e4db3b2e10efe36deeaa4/
2024-09-10 17:32:35 +02:00
kolaente e698ac5a34 fix(label): ignore existing ID during creation 2024-09-10 16:45:40 +02:00
renovate 020fea686d fix(deps): update module github.com/getsentry/sentry-go to v0.29.0 2024-09-10 10:17:45 +00:00
renovateandkonrad addf8523f4 fix(deps): update dependency express to v4.20.0 2024-09-10 09:49:23 +00:00
renovate 044aa1c742 fix(deps): update dependency vue to v3.5.4 2024-09-10 09:14:50 +00:00
renovateandkonrad 552e1ce796 chore(deps): update goreleaser/nfpm docker tag to v2.40.0 (#2647)
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-09-10 08:50:42 +00:00
renovateandkonrad c1246d73d4 fix(deps): update dependency @sentry/vue to v8.29.0 2024-09-10 07:13:00 +00:00
renovate 85fa397a46 chore(deps): update pnpm to v9.10.0 2024-09-09 14:35:34 +00:00
renovate af47e43c34 chore(deps): update dev-dependencies 2024-09-09 03:31:05 +00:00
Frederick [Bot]andkolaente 2539f15dfb [skip ci] Updated swagger docs 2024-09-07 11:48:49 +00:00
kolaente 5c1b2846a1 docs(api): use correct return type for the /user endpoint 2024-09-07 13:27:06 +02:00
renovate af8100d262 fix(deps): update dependency vue-router to v4.4.3 2024-09-06 18:17:15 +00:00
renovateandkonrad 63f85c97ae chore(deps): update dependency go to v1.23.1 2024-09-06 18:06:24 +00:00
renovateandkonrad 2b1e3ffd2a chore(deps): update dev-dependencies 2024-09-06 18:04:14 +00:00
renovate 5f470afd7c fix(deps): update module golang.org/x/image to v0.20.0 2024-09-06 17:17:31 +00:00
renovateandkonrad 37c6f94ba2 fix(deps): update module github.com/prometheus/client_golang to v1.20.3 2024-09-06 15:56:25 +00:00
renovate b42cd5786e fix(deps): update dependency vue to v3.5.3 2024-09-06 15:21:22 +00:00
renovateandkonrad 5072ab3d72 fix(deps): update module dario.cat/mergo to v1.0.1 2024-09-06 15:11:42 +00:00
renovateandkonrad 4b5ec018f5 fix(deps): update module github.com/redis/go-redis/v9 to v9.6.1 2024-09-06 15:11:34 +00:00
renovateandkonrad efe5c7b3cb fix(deps): update module golang.org/x/crypto to v0.27.0 2024-09-06 15:09:51 +00:00
renovateandkonrad ba0c32aacf fix(deps): update module golang.org/x/oauth2 to v0.23.0 2024-09-06 15:09:43 +00:00
renovateandkonrad b6e142e8d0 fix(deps): update vueuse to v11 2024-09-06 15:08:03 +00:00
renovate 750d565477 fix(deps): update dependency tailwindcss to v3.4.10 2024-09-06 12:15:45 +00:00
Frederick [Bot]andkolaente 2b8515b91d [skip ci] Updated swagger docs 2024-09-06 11:18:51 +00:00
kolaente 68636f27da fix(files): use absolute path everywhere 2024-09-06 12:59:48 +02:00
kolaente cf94cc8cab chore(docs): clarify usage of related model creation
Resolves https://github.com/go-vikunja/vikunja/issues/323
2024-09-06 12:06:12 +02:00
renovateandkonrad 669b7452cd fix(deps): update dependency axios to v1.7.7 2024-09-06 08:34:52 +00:00
renovateandkonrad 1601c9098d fix(deps): update dependency sortablejs to v1.15.3 2024-09-06 08:34:36 +00:00
renovateandkonrad f132b0e718 fix(deps): update module github.com/gabriel-vasile/mimetype to v1.4.5 2024-09-06 08:33:40 +00:00
renovateandkonrad 5d12986e3f fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.23 2024-09-06 08:33:37 +00:00
renovateandkonrad 74e0197dc6 fix(deps): update module github.com/threedotslabs/watermill to v1.3.7 2024-09-06 08:32:06 +00:00
renovateandkonrad 2dd133fb4c fix(deps): update dependency @kyvg/vue3-notification to v3.3.0 2024-09-06 08:31:54 +00:00
kolaente 02c1de55c4 chore(attachments): refactor building image preview 2024-09-06 09:43:59 +02:00
renovateandkonrad cbc63c853d fix(deps): update dependency @sentry/vue to v8.28.0 2024-09-06 07:40:44 +00:00
renovateandkonrad b5c4a3a80c fix(deps): update dependency pinia to v2.2.2 2024-09-06 07:40:14 +00:00
renovateandkonrad a8ea5e11ab fix(deps): update dependency vue-i18n to v9.14.0 2024-09-06 07:39:02 +00:00
renovateandkonrad 812430b9cf fix(deps): update module golang.org/x/text to v0.18.0 2024-09-06 07:37:53 +00:00
kolaente 9d7b6d3d9a feat(dump): add flag to allow specifying dump path
Resolves https://community.vikunja.io/t/vikunja-0-24-docker-bin-sh/2621/3
2024-09-05 15:14:03 +02:00
kolaente c2b116de70 chore(files): use absolute file path to retrieve and save files 2024-09-05 15:03:32 +02:00
kolaente 22e594e253 fix(kanban): save updated position to store
This fixes a bug where the position of a task would not be calculated correctly when the task was moved next to another recently moved task. The problem was caused by the calculation of the new position referring to the old value of the position attribute, because it was not updated in the local store.

Resolves https://community.vikunja.io/t/kanban-cards-in-wrong-order/2731/6
2024-09-05 14:51:47 +02:00
kolaente 0d9c03e0f2 chore(magefile): use tx.Sync instead of Sync2 2024-09-05 14:40:22 +02:00
renovateandkonrad 72bb6609a1 fix(deps): update module golang.org/x/term to v0.24.0 2024-09-05 10:18:27 +00:00
renovate b259072e6a fix(deps): update module github.com/typesense/typesense-go to v2 2024-09-04 21:16:13 +00:00
kolaente ffcc48ec87 fix(caldav): make sure colors are correctly saved and returned
Resolves https://community.vikunja.io/t/caldav-sync-tasks-org-strips-colour-and-end-date-values/2753/2
2024-09-04 23:08:29 +02:00
Frederick [Bot]andkolaente 5aa0b6a0cf [skip ci] Updated swagger docs 2024-09-04 20:43:53 +00:00
kolaente b60efbd259 chore(subscription): return subscription entity type using json Marshaler 2024-09-04 22:25:03 +02:00
kolaente 34ac29fcce fix(reminders): notify subscribed users as well
Resolves https://community.vikunja.io/t/no-reminder-notification-by-e-mail-from-my-colleague/2779
2024-09-04 22:11:07 +02:00
kolaente 75f3e930cd fix(subscriptions): do not panic when a task does not have a subscription 2024-09-04 21:55:14 +02:00
kolaente 4ff8815fe1 fix(subscriptions): cleanup and simplify fetching subscribers for tasks and projects logic
Vikunja now uses one recursive CTE and a few optimizations to fetch all subscribers for a task or project. This makes the relevant code easier to maintain and more performant.
2024-09-04 19:55:17 +02:00
renovate 850ec7efb0 fix(deps): update dependency dayjs to v1.11.13 2024-09-03 20:59:37 +00:00
kolaente 115d1c3618 fix(subscription): always return task subscription when subscribed to task and project 2024-09-03 22:37:53 +02:00
kolaente 6d26497bbb chore(deps): update github.com/wneessen/go-mail to v0.4.4 2024-09-03 22:07:11 +02:00
kolaente 06305eb6b3 fix(subscriptions): correctly inherit subscriptions
Resolves https://community.vikunja.io/t/e-mail-notification-twice/2740/20
2024-09-03 22:04:11 +02:00
Frederick [Bot]anddrone 7bd84a845c chore(i18n): update translations via Crowdin 2024-09-03 00:25:53 +00:00
kolaente 165ee9e7f3 fix(service worker): use correct workbox version
Resolves https://github.com/go-vikunja/vikunja/issues/322
2024-09-02 23:40:17 +02:00
kolaente 2c9becec10 fix(notifications): only add project subscription as task subscription when the user is not already subscribed to the task
Before this fix, a project subscription object was added twice to the list of subscriptions for a task when the user did not subscribe to the task directly. This caused the user to receive a comment notification twice for a given task.
This was probably a regression from efde364224.

Resolves https://community.vikunja.io/t/e-mail-notification-twice/2740/18
2024-09-02 23:36:58 +02:00
kolaente 1463bce24c feat(dev): add mailpit to devenv services 2024-09-02 23:18:50 +02:00
kolaente 5c56d07215 chore(web): always set internal error 2024-09-01 19:37:27 +02:00
kolaente 6fb314b326 chore(web): use logger directly 2024-09-01 19:37:27 +02:00
kolaente 9b01666ec6 chore(web): use web auth factory directly 2024-09-01 19:37:27 +02:00
Frederick [Bot]anddrone 14c862d284 chore(i18n): update translations via Crowdin 2024-08-30 00:25:55 +00:00
kolaente bcfd72c645 chore(web): use config directly 2024-08-29 18:41:20 +02:00
kolaente 499f66b7ae chore(web): directly use new db session 2024-08-29 16:31:46 +02:00
kolaente 8e37d5cb76 chore(web): remove redundant use of fmt.Sprintf 2024-08-29 16:28:37 +02:00
kolaente 57ba073874 chore(web): use errors.As instead of type assertion 2024-08-29 16:28:16 +02:00
kolaente 329de3aab3 chore(web): remove unused echo context 2024-08-29 16:20:39 +02:00
kolaente 74a74b7ec7 fix(labels): test error assertion 2024-08-29 16:16:41 +02:00
kolaente 2063da9eec chore(web): move web handler package to Vikunja 2024-08-29 16:15:28 +02:00
kolaente 7a7e97770c chore(errors): always add internal error to echo error 2024-08-29 15:39:34 +02:00
kolaente bea131cfd9 fix(labels): trigger task.updated event when removing a label from a task 2024-08-29 15:19:06 +02:00
kolaente 5a0d1f1dc6 feat(event): simplify dispatching task updated event from only a task id 2024-08-29 15:18:03 +02:00
kolaente b5035f9d2b chore(docker): use new env format 2024-08-29 11:19:46 +02:00
kolaente 07bd9de0c6 feat(docker): add opencontainer labels
Resolves https://github.com/go-vikunja/vikunja/issues/308
2024-08-29 11:19:21 +02:00
kolaente efde364224 fix(subscriptions): ignore task subscription when the user is subscribed to the project
Resolves https://community.vikunja.io/t/e-mail-notification-twice/2740/12
Resolves https://github.com/go-vikunja/vikunja/issues/316
2024-08-29 10:35:05 +02:00
kolaente fde1763eef fix(api): return 404 response when using a token and the route does not exist 2024-08-29 10:12:20 +02:00
kolaente 429c7ca2c1 feat(task): always insert new tasks at the top
Resolves https://community.vikunja.io/t/kanban-cards-in-wrong-order/2731
2024-08-28 15:30:40 +02:00
kolaente bf56311faa fix(task): set done at date when moving a task to the done bucket
Resolves https://github.com/go-vikunja/vikunja/issues/320
2024-08-28 14:21:31 +02:00
Frederick [Bot]anddrone c38aaeb224 chore(i18n): update translations via Crowdin 2024-08-28 00:25:41 +00:00
Frederick [Bot]anddrone 1b9139b9b0 chore(i18n): update translations via Crowdin 2024-08-27 00:25:53 +00:00
kolaente 13763e3540 chore(deps): update tiptap to 2.6.6 2024-08-26 22:58:15 +02:00
kolaente 56bab7485f feat(i18n): add Lithuanian translation for selection 2024-08-26 17:05:01 +02:00
kolaente 8bfd0493b2 fix(deps): update golangci 2024-08-25 14:30:46 +00:00
kolaente 435cb2e7f7 fix(view): do not crash when saving a view
Resolves https://github.com/go-vikunja/vikunja/issues/312
2024-08-25 15:53:51 +02:00
Frederick [Bot]anddrone 9ed33f5c08 chore(i18n): update translations via Crowdin 2024-08-23 00:25:56 +00:00
Frederick [Bot]anddrone 83669fd9e0 chore(i18n): update translations via Crowdin 2024-08-21 00:25:43 +00:00
Frederick [Bot]anddrone 35578e0021 chore(i18n): update translations via Crowdin 2024-08-20 00:25:51 +00:00
Frederick [Bot]anddrone e281de30b5 chore(i18n): update translations via Crowdin 2024-08-17 00:15:36 +00:00
Frederick [Bot]anddrone ec2c2e74f0 chore(i18n): update translations via Crowdin 2024-08-15 00:03:11 +00:00
kolaente b031c9772f chore: 0.24.2 release preperations
(cherry picked from commit 08821ea8a8)
2024-08-12 20:25:31 +02:00
kolaente f22420d502 fix(kanban): always make cover image full width 2024-08-12 17:22:26 +02:00
kolaente ebfd5f54d2 fix(migration): ensure project background gets exported and imported 2024-08-12 17:18:07 +02:00
kolaente 28b4eaee31 fix(migration): make sure tasks are associated to the correct view and bucket for data imported from Vikunja dump
This change fixes a bug where imported projects would contain the default views additionally to the ones included in the export. This also caused the tasks to not show up in the views and buckets where they should show up, the newly imported ones.

Resolves https://community.vikunja.io/t/migration-from-vikunja-export-duplicated-boards-local-to-oidc/2690
2024-08-12 17:00:16 +02:00
kolaente b83448b7a6 fix(views): do not create task bucket and task position entries when duplicating a project
Resolves https://github.com/go-vikunja/vikunja/issues/297
Resolves https://community.vikunja.io/t/duplicating-a-board-puts-the-buckets-in-the-original-board/2579
2024-08-12 15:53:51 +02:00
kolaente eed783e42f fix(task): move task into new kanban bucket when moving between projects
Resolves https://github.com/go-vikunja/vikunja/issues/290
Resolves https://community.vikunja.io/t/tasks-moved-from-one-project-to-another-do-not-show-up-in-kanban/2689
2024-08-12 15:35:37 +02:00
kolaente 1d624612ee chore: use nixpkgs unstable for more recent packages 2024-08-12 15:32:12 +02:00
kolaente 9576f72d7c fix(task): set current project after moving a task 2024-08-12 15:22:14 +02:00
kolaente 7efc4d1bc8 fix(task): do not allow moving a task to the project the task already belongs to 2024-08-12 15:18:45 +02:00
kolaente 670e605275 chore(deps): update dependency node to v20.16.0 2024-08-12 12:26:53 +02:00
kolaente 871b3ce071 chore(deps): update go toolchain to 1.22.5 2024-08-12 12:17:53 +02:00
kolaente ad3c5fcee5 feat: switch from nix flakes to devenv 2024-08-12 12:17:31 +02:00
kolaente e2a87036e0 feat(editor): support custom protocol for links
Resolves https://github.com/go-vikunja/vikunja/issues/306
2024-08-12 11:42:43 +02:00
kolaente 40bb86bee5 fix(mail): do not fail testmail command when the connection could not be closed.
Resolves https://github.com/go-vikunja/vikunja/issues/300
2024-08-12 11:08:16 +02:00
Frederick [Bot]anddrone 4a4ed2cf89 chore(i18n): update translations via Crowdin 2024-08-10 00:03:08 +00:00
kolaente 7751e124a7 chore(deps): update flake 2024-08-08 11:24:26 +02:00
Frederick [Bot]anddrone 6137d07138 chore(i18n): update translations via Crowdin 2024-08-08 00:03:17 +00:00
kolaente 66d316f0b5 fix(i18n): change casing of Ukrainian language in selector 2024-08-07 16:51:22 +02:00
kolaente bf2d56c9d4 docs: clarify Todoist redirect url
Resolves https://github.com/go-vikunja/vikunja/issues/302
2024-08-07 16:27:29 +02:00
Dominik Pschenitschniandkonrad 413798b321 fix: remove console log 2024-08-07 13:29:56 +00:00
Frederick [Bot]anddrone bf1c8c6119 chore(i18n): update translations via Crowdin 2024-07-31 00:02:59 +00:00
Frederick [Bot]anddrone 49fa0a8468 chore(i18n): update translations via Crowdin 2024-07-27 00:03:15 +00:00
Frederick [Bot]anddrone 784cd42a7a chore(i18n): update translations via Crowdin 2024-07-24 00:03:08 +00:00
Frederick [Bot]andDominik Pschenitschni 4f74be0247 chore(i18n): update translations via Crowdin 2024-07-23 00:02:28 +00:00
Dominik Pschenitschniandkonrad 7ef1e0a3e5 fix: textarea autosize for LanguageTool 2024-07-21 12:40:32 +00:00
Dominik Pschenitschniandkonrad bc5fd380e5 chore: improve error message 2024-07-21 11:03:20 +00:00
Dominik Pschenitschniandkonrad bcd306b84d chore: remove lodash.debounce 2024-07-21 11:02:25 +00:00
Dominik Pschenitschniandkonrad ad2870335d feat: add missing peer dependency 2024-07-21 11:01:38 +00:00
Frederick [Bot]anddrone 917748777f chore(i18n): update translations via Crowdin 2024-07-21 00:03:15 +00:00
renovateandkonrad 324c966dda fix(deps): update module github.com/coreos/go-oidc/v3 to v3.11.0 2024-07-19 10:16:39 +00:00
Frederick [Bot]andDominik Pschenitschni 0984821b41 [skip ci] Updated swagger docs 2024-07-19 10:01:46 +00:00
Dominik Pschenitschniandkonrad e8be657d97 feat: improve ProjectSettingsViews 2024-07-19 09:43:49 +00:00
75ce261f74 feat: downscaled image previews for task attachments (#2541)
As discussed in [https://community.vikunja.io/t/add-scaled-down-images-for-image-previews](https://community.vikunja.io/t/add-scaled-down-images-for-image-previews) this adds a query parameter in the task attachment request which returns a scaled down image for preview purposes to reduce network load and improve responsiveness.

Co-authored-by: Elscrux <nickposer2102@gmail.com>
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2541
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Elscrux <elscrux@gmail.com>
Co-committed-by: Elscrux <elscrux@gmail.com>
2024-07-19 09:30:06 +00:00
5f9d0fe763 feat: permalinks for task comments (#2442)
Resolves #2441

Co-authored-by: Devadatta Sahoo <webmaster@nettantra.com>
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2442
Reviewed-by: konrad <k@knt.li>
Co-authored-by: devadattas <devadattas@gmail.com>
Co-committed-by: devadattas <devadattas@gmail.com>
2024-07-19 08:45:50 +00:00
Dominik Pschenitschniandkonrad bbfd5270db feat: add tailwind with prefix (#2513)
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2513
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-committed-by: Dominik Pschenitschni <mail@celement.de>
2024-07-19 07:33:10 +00:00
Dominik Pschenitschniandkonrad 1e632397d2 fix(projects): trigger only single mutation
Previously these store methods created multiple edits on deep objects in the store or edited a deep nested object directly.

This is bad because:
- multiple edits lead to multiple triggers of all the watchers on the project.
- in theory we should listen deep to a project if we use some deep reactive value of a project, if we want deep updates. Because this is easy to forget, it's better to update the project directly. For this the method `setProject` already existed in the store. This is no real overhead because Vue is smart enough to only trigger listeners that use data of the modified state.

By modifying only a copy of the view and submitting the modified result __once__ we can save us a lot of headache.

PS: I'm not sure if there were any visible problems, because Vue is really fast and the reactivity system works quite well. Regardless of this we should try not to modify state unnecessarily.
2024-07-19 07:26:50 +00:00
Dominik Pschenitschniandkonrad d35454c099 feat: improve priority visibility 2024-07-19 06:53:53 +00:00
Dominik Pschenitschniandkonrad 2a6ba7e7f0 feat: improve label store 2024-07-19 06:51:24 +00:00
Dominik Pschenitschniandkonrad 1e523a1a39 feat: improve projects store 2024-07-19 05:49:14 +00:00
Dominik Pschenitschniandkonrad fb91e73a3c fix: emit for DatepickerWithValues 2024-07-19 05:28:17 +00:00
Frederick [Bot]andDominik Pschenitschni ebd6139c1f chore(i18n): update translations via Crowdin 2024-07-19 00:03:11 +00:00
Dominik Pschenitschni 6990be705c feat: use withDefaults for Reminders 2024-07-18 18:31:33 +02:00
48 changed files with 2771 additions and 3313 deletions
+82 -80
View File
@@ -580,7 +580,7 @@ steps:
event: [ push, tag, pull_request ]
- name: before-static-build
image: ghcr.io/techknowlogick/xgo:go-1.23.x
image: techknowlogick/xgo:latest
pull: always
commands:
- export PATH=$PATH:$GOPATH/bin
@@ -589,7 +589,7 @@ steps:
depends_on: [ fetch-tags, mage ]
- name: static-build-windows
image: ghcr.io/techknowlogick/xgo:go-1.23.x
image: techknowlogick/xgo:latest
pull: always
environment:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
@@ -605,7 +605,7 @@ steps:
- frontend-build
- name: static-build-linux
image: ghcr.io/techknowlogick/xgo:go-1.23.x
image: techknowlogick/xgo:latest
pull: always
environment:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
@@ -621,7 +621,7 @@ steps:
- frontend-build
- name: static-build-darwin
image: ghcr.io/techknowlogick/xgo:go-1.23.x
image: techknowlogick/xgo:latest
pull: always
environment:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
@@ -647,7 +647,7 @@ steps:
- ./mage-static release:compress
- name: after-build-static
image: ghcr.io/techknowlogick/xgo:go-1.23.x
image: techknowlogick/xgo:latest
pull: always
depends_on:
- after-build-compress
@@ -676,13 +676,13 @@ steps:
image: plugins/s3
pull: always
settings:
bucket: vikunja
bucket: vikunja-releases
access_key:
from_secret: hetzner_access_key_id
from_secret: aws_access_key_id
secret_key:
from_secret: hetzner_secret_access_key
endpoint: https://fsn1.your-objectstorage.com
region: fsn1
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: dist/zip/
source: dist/zip/*
@@ -698,13 +698,13 @@ steps:
image: plugins/s3
pull: always
settings:
bucket: vikunja
bucket: vikunja-releases
access_key:
from_secret: hetzner_access_key_id
from_secret: aws_access_key_id
secret_key:
from_secret: hetzner_secret_access_key
endpoint: https://fsn1.your-objectstorage.com
region: fsn1
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: dist/zip/
source: dist/zip/*
@@ -750,13 +750,13 @@ steps:
image: plugins/s3
pull: always
settings:
bucket: vikunja
bucket: vikunja-releases
access_key:
from_secret: hetzner_access_key_id
from_secret: aws_access_key_id
secret_key:
from_secret: hetzner_secret_access_key
endpoint: https://fsn1.your-objectstorage.com
region: fsn1
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: dist/os-packages/
source: dist/os-packages/*
@@ -772,13 +772,13 @@ steps:
image: plugins/s3
pull: always
settings:
bucket: vikunja
bucket: vikunja-releases
access_key:
from_secret: hetzner_access_key_id
from_secret: aws_access_key_id
secret_key:
from_secret: hetzner_secret_access_key
endpoint: https://fsn1.your-objectstorage.com
region: fsn1
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: dist/os-packages/
source: dist/os-packages/*
@@ -837,6 +837,7 @@ steps:
repo: vikunja/vikunja
tags: unstable
platforms:
- linux/386
- linux/amd64
- linux/arm/v6
- linux/arm/v7
@@ -868,6 +869,7 @@ steps:
from_secret: docker_password
repo: vikunja/vikunja
platforms:
- linux/386
- linux/amd64
- linux/arm/v6
- linux/arm/v7
@@ -929,13 +931,13 @@ steps:
image: plugins/s3
pull: always
settings:
bucket: vikunja
bucket: vikunja-releases
access_key:
from_secret: hetzner_access_key_id
from_secret: aws_access_key_id
secret_key:
from_secret: hetzner_secret_access_key
endpoint: https://fsn1.your-objectstorage.com
region: fsn1
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
source: frontend/vikunja-frontend-unstable.zip
target: /
@@ -988,13 +990,13 @@ steps:
image: plugins/s3
pull: always
settings:
bucket: vikunja
bucket: vikunja-releases
access_key:
from_secret: hetzner_access_key_id
from_secret: aws_access_key_id
secret_key:
from_secret: hetzner_secret_access_key
endpoint: https://fsn1.your-objectstorage.com
region: fsn1
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
source: frontend/vikunja-frontend-${DRONE_TAG##v}.zip
target: /
@@ -1093,8 +1095,8 @@ steps:
# settings:
# restore: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://fsn1.your-objectstorage.com
# region: fsn1
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
@@ -1131,8 +1133,8 @@ steps:
# settings:
# rebuild: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://fsn1.your-objectstorage.com
# region: fsn1
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
@@ -1174,8 +1176,8 @@ steps:
# settings:
# restore: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://fsn1.your-objectstorage.com
# region: fsn1
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
@@ -1198,30 +1200,30 @@ steps:
- unzip vikunja-frontend-$$VERSION.zip -d frontend
- sed -i 's/\\/api\\/v1//g' frontend/index.html
- ./bumpp.sh
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm install --fetch-timeout 100000
- pnpm dist --linux --windows
# - name: rebuild-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# rebuild: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://fsn1.your-objectstorage.com
# region: fsn1
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
# - '.cache'
# depends_on:
# - build
- yarn install
- cat package.json
- yarn dist --linux --windows
# - name: rebuild-cache
# image: meltwater/drone-cache:dev
# pull: true
# environment:
# AWS_ACCESS_KEY_ID:
# from_secret: cache_aws_access_key_id
# AWS_SECRET_ACCESS_KEY:
# from_secret: cache_aws_secret_access_key
# settings:
# rebuild: true
# bucket: kolaente.dev-drone-dependency-cache
# endpoint: https://s3.fr-par.scw.cloud
# region: fr-par
# path_style: true
# cache_key: '{{ .Repo.Name }}_{{ checksum "desktop/yarn.lock" }}_{{ arch }}_{{ os }}'
# mount:
# - '.cache'
# depends_on:
# - build
- name: rename-unstable
image: bash
pull: true
@@ -1239,13 +1241,13 @@ steps:
image: plugins/s3
pull: true
settings:
bucket: vikunja
bucket: vikunja-releases
access_key:
from_secret: hetzner_access_key_id
from_secret: aws_access_key_id
secret_key:
from_secret: hetzner_secret_access_key
endpoint: https://fsn1.your-objectstorage.com
region: fsn1
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: desktop/dist/
source: desktop/dist/Vikunja-Desktop*
@@ -1261,13 +1263,13 @@ steps:
image: plugins/s3
pull: true
settings:
bucket: vikunja
bucket: vikunja-releases
access_key:
from_secret: hetzner_access_key_id
from_secret: aws_access_key_id
secret_key:
from_secret: hetzner_secret_access_key
endpoint: https://fsn1.your-objectstorage.com
region: fsn1
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: desktop/dist/
source: desktop/dist/*
@@ -1296,9 +1298,9 @@ steps:
# - name: build
# environment:
# ACCESS_KEY:
# from_secret: hetzner_access_key_id
# from_secret: aws_access_key_id
# SECRET_KEY:
# from_secret: hetzner_secret_access_key
# from_secret: aws_secret_access_key
# commands:
# - git fetch --tags
# - export VERSION=${DRONE_TAG##v}
@@ -1311,9 +1313,9 @@ steps:
# - sed -i '' "s/\$${version}/$$VERSION/g" package.json
# - yarn install
# - yarn dist --mac
# - mc config host add scw-fsn1 https://fsn1.your-objectstorage.com $ACCESS_KEY $SECRET_KEY --api S3v4
# - mc cp ./dist/*.dmg scw-fsn1/vikunja/desktop/$VERSION/
# - mc cp ./dist/*.dmg.blockmap scw-fsn1/vikunja/desktop/$VERSION/
# - mc config host add scw-fr-par https://s3.fr-par.scw.cloud $ACCESS_KEY $SECRET_KEY --api S3v4
# - mc cp ./dist/*.dmg scw-fr-par/vikunja-releases/desktop/$VERSION/
# - mc cp ./dist/*.dmg.blockmap scw-fr-par/vikunja-releases/desktop/$VERSION/
---
kind: pipeline
@@ -1350,6 +1352,6 @@ steps:
- failure
---
kind: signature
hmac: 02fb3097f3e50facb2579de5c3b94e513b97e535a049a28ddb67f85e494d65b1
hmac: eff6e40dc763fa1395078daaabcbef2aaa74fe0a36818a87e89015dbc4e9dae4
...
-261
View File
@@ -7,267 +7,6 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/vikunja/releases.
## [0.24.6] - 2024-12-22
### Bug Fixes
* *(export)* Update only current user export file id
### Features
* Use hetzner object storage for releases ([3f98f47](3f98f47256628ac559a947f6c9541c0bf03001b6))
### Miscellaneous Tasks
* Release preparation ([17b2810](17b281072f095730a0fbb28458d713fa883f43ec))
* Sign drone config ([b91a5d9](b91a5d9adfb7144126f5824224970ad4015dcd6a))
* Do not generate config yaml ([8c28596](8c285968afff7ebd087ce824b52b0a9f28a8ec20))
## [0.24.5] - 2024-11-21
### Bug Fixes
* *(attachments)* Check permissions when accessing all attachments
* *(saved filters)* Check permissions when accessing tasks of a filter
* Pin xgo to 1.22.x ([87b2aac](87b2aaccb8cdcbe1ecb6092951a0bfe224ad7006))
* Upgrade xgo ([19b63c8](19b63c86c51f67614b867c75a58cda1774685edd))
* Upgrade xgo docker image everywhere ([04b40f8](04b40f8a7dcd01a86ddb8b27596073d1e50f9e97))
* *(ci)* Do not build linux 368 docker images
* Disable 368 releases ([73db10f](73db10fb02268e07d29842493df55f4d645ac503))
- **BREAKING**: disable 368 releases
### Miscellaneous Tasks
* Sign drone config ([17c4878](17c487875b5771c0971ee8bf030807171de2dddc))
* Go mod tidy ([9639025](96390257e0911089ae33a9565e8be7fa954c772c))
## [0.24.4] - 2024-09-29
### Bug Fixes
* *(attachment)* Do not use image previews
* *(checkbox)* Use sibling css selector instead of has
* *(files)* Only use service rootpath for files when the files path is not absolute
* *(filters)* Explicitly search in json when using postgres
* *(task)* Paginate task comments
* *(task)* Do not show close button when the task was not opened via modal
* *(task)* Improve task delete modal on mobile
* *(test)* Use correct selector for modal header
* Partial fix to allow list tasks in ios reminders app (#2717)
* *(attachments)* Revert "chore(attachments): refactor building image preview"
### Dependencies
* *(deps)* Update desktop lockfile
* *(deps)* Update dependency vue to v3.5.7
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency vue-i18n to v10.0.2
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency vue to v3.5.8
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v5.1.0
* *(deps)* Update dependency vue-i18n to v10.0.3
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v5.2.0
* *(deps)* Update dependency @sentry/vue to v8.31.0
* *(deps)* Update dependency tailwindcss to v3.4.13
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency @sentry/vue to v8.32.0
* *(deps)* Update tiptap to v2.7.3
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency vue to v3.5.9
* *(deps)* Update dependency dompurify to v3.1.7
* *(deps)* Update tiptap to v2.7.4
* *(deps)* Update dependency vue to v3.5.10
* *(deps)* Update dev-dependencies
## [0.24.3] - 2024-09-20
### Bug Fixes
* *(a11y)* Hide unfocusable buttons
* *(api)* Return 404 response when using a token and the route does not exist
* *(auth)* Restrict max password length to 72 bytes
* *(caldav)* Make sure colors are correctly saved and returned
* *(caldav)* Reject invalid project id with error 400
* *(editor)* Restore the current value, not the one from a previous task
* *(files)* Use absolute path everywhere
* *(filter)* Do not replace labels keyword when the value is 'label'
* *(filter)* Make sure tasks are in a correct bucket and position when they are part of a date filter
* *(filters)* Immediately propagate changes
* *(filters)* Do not replace filter or project values when the id value resolves to undefined
* *(filters)* Correctly transform and populate saved filter when creating and editing
* *(home)* Explicitly use filter for tasks on home page when one is set
* *(kanban)* Save updated position to store
* *(kanban)* Make task creation loading spinner actually visible
* *(kanban)* Make kanban full width on mobile
* *(kanban)* Do not mark first bucked as done bucket in filter bucket mode
* *(kanban)* Correctly paginate filtered kanban buckets
* *(label)* Ignore existing ID during creation
* *(labels)* Trigger task.updated event when removing a label from a task
* *(labels)* Test error assertion
* *(labels)* Remove input interactivity when label edit is disabled
* *(labels)* Trigger task updated for bulk label task update
* *(modal)* Make sure modal and its content scrolls properly on mobile
* *(modal)* Do not prevent scrolling on mobile
* *(modal)* Make scrolling on iOS Safari work
* *(multiselect)* Make selectPlaceholder optional
* *(notifications)* Only add project subscription as task subscription when the user is not already subscribed to the task
* *(password)* Validate password before sending request to api
* *(project)* Show description in title attribute without html
* *(project)* Reset id before creating
* *(projects)* Do not hide 6th project on project overview
* *(projects)* Description not visible on mobile
* *(reminders)* Notify subscribed users as well
* *(service worker)* Use correct workbox version
* *(subscription)* Always return task subscription when subscribed to task and project
* *(subscriptions)* Ignore task subscription when the user is subscribed to the project
* *(subscriptions)* Correctly inherit subscriptions
* *(subscriptions)* Cleanup and simplify fetching subscribers for tasks and projects logic
* *(subscriptions)* Do not panic when a task does not have a subscription
* *(table)* Make sorting for two-word properties work
* *(task)* Set done at date when moving a task to the done bucket
* *(task)* Specify task index when creating multiple tasks at once
* *(task)* Cyclomatic complexity
* *(task)* Make print styles work when printing task detail view from kanban
* *(task)* Multiple overlapping defer due date popups
* *(task)* Align task title on mobile popup
* *(task)* Dragging and dropping on mobile
* *(task)* Add task to filter view after it was updated
* *(task)* Cleanup old task positions and task buckets when adding an updated or created task to filter
* *(task)* Mark related task as done from the task detail view
* *(task)* Open focused task when pressing enter
* *(test)* Cypress test selector
* *(typesense)* Only fail silently when a project was not found during indexing
* *(typesense)* Add new tasks to typesense properly
* *(typesense)* Make sure task positions are recreated properly when updating them
* *(typesense)* Use emplace instead of upsert to update documents
* *(typesense)* Index tasks one by one
* *(typesense)* Force position to always be float instead of auto-inferring
* *(typesense)* Use typesense bulk insert, log all errors
* *(user)* Do not create user with existing id
* *(view)* Do not crash when saving a view
* *(view)* Correctly resolve label for filtered views or buckets
* *(view)* Correctly resolve bucket filter when paginating
* *(view)* Correctly get paginated task results
* *(views)* Add migration for filtered kanban buckets* Lint ([53d62d3](53d62d35f4488940a96d755de93ded64b8ac34a3))
* Reset id before creating ([93f7dd6](93f7dd611ad288a149f5da5463867d224334815f))
* Test selector ([063aa7a](063aa7afec717c3ed05be9d2ca73bde3d0bd8d35))
### Dependencies
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v5
* *(deps)* Update dependency @kyvg/vue3-notification to v3.3.0
* *(deps)* Update dependency @sentry/vue to v8.28.0
* *(deps)* Update dependency @sentry/vue to v8.29.0
* *(deps)* Update dependency @sentry/vue to v8.30.0
* *(deps)* Update dependency axios to v1.7.7
* *(deps)* Update dependency date-fns to v4
* *(deps)* Update dependency dayjs to v1.11.13
* *(deps)* Update dependency express to v4.20.0
* *(deps)* Update dependency express to v4.21.0
* *(deps)* Update dependency go to v1.23.1
* *(deps)* Update dependency pinia to v2.2.2
* *(deps)* Update dependency sortablejs to v1.15.3
* *(deps)* Update dependency tailwindcss to v3.4.10
* *(deps)* Update dependency tailwindcss to v3.4.11
* *(deps)* Update dependency tailwindcss to v3.4.12
* *(deps)* Update dependency vue to v3.5.3
* *(deps)* Update dependency vue to v3.5.4
* *(deps)* Update dependency vue to v3.5.5
* *(deps)* Update dependency vue to v3.5.6
* *(deps)* Update dependency vue-i18n to v10
* *(deps)* Update dependency vue-i18n to v10.0.1
* *(deps)* Update dependency vue-i18n to v9.14.0
* *(deps)* Update dependency vue-router to v4.4.3
* *(deps)* Update dependency vue-router to v4.4.4
* *(deps)* Update dependency vue-router to v4.4.5
* *(deps)* Update dependency vuemoji-picker to v0.3.1* Chore(deps): update goreleaser/nfpm docker tag to v2.40.0 (#2647)
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update github.com/wneessen/go-mail to v0.4.4
* *(deps)* Update golangci
* *(deps)* Update module dario.cat/mergo to v1.0.1
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.5
* *(deps)* Update module github.com/getsentry/sentry-go to v0.29.0
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.23
* *(deps)* Update module github.com/prometheus/client_golang to v1.20.3
* *(deps)* Update module github.com/prometheus/client_golang to v1.20.4
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.6.1
* *(deps)* Update module github.com/threedotslabs/watermill to v1.3.7
* *(deps)* Update module github.com/typesense/typesense-go to v2
* *(deps)* Update module github.com/typesense/typesense-go to v2
* *(deps)* Update module golang.org/x/crypto to v0.27.0
* *(deps)* Update module golang.org/x/image to v0.20.0
* *(deps)* Update module golang.org/x/oauth2 to v0.23.0
* *(deps)* Update module golang.org/x/term to v0.24.0
* *(deps)* Update module golang.org/x/text to v0.18.0
* *(deps)* Update pnpm to v9.10.0
* *(deps)* Update tiptap to 2.6.6
* *(deps)* Update tiptap to v2.7.0
* *(deps)* Update tiptap to v2.7.1
* *(deps)* Update tiptap to v2.7.2
* *(deps)* Update vueuse to v11
* *(deps)* Update vueuse to v11.1.0
* *(deps)*: update dependency flexsearch to v0.7.43 (#2095)
* *(deps)*: update golangci/golangci-lint docker tag to v1.61.0 (#2678)
### Documentation
* *(api)* Use correct return type for the /user endpoint
### Features
* *(event)* Simplify dispatching task updated event from only a task id
* *(navigation)* Use focus-visible for nav items
* *(task)* Use focus-visible for task focus styles
### Miscellaneous Tasks
* *(attachments)* Refactor building image preview
* *(devenv)* Do not install cypress on darwin
* *(docker)* Use new env format
* *(docs)* Clarify usage of related model creation
* *(errors)* Always add internal error to echo error
* *(files)* Use absolute file path to retrieve and save files
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(logging)* Simplify log template string
* *(magefile)* Use tx.Sync instead of Sync2
* *(subscription)* Return subscription entity type using json Marshaler
* *(tasks)* Move drag options to direct attributes instead of v-bind
* *(typesense)* Add more debug logging
* *(web)* Move web handler package to Vikunja
* *(web)* Remove unused echo context
* *(web)* Use errors.As instead of type assertion
* *(web)* Remove redundant use of fmt.Sprintf
* *(web)* Directly use new db session
* *(web)* Use config directly
* *(web)* Use web auth factory directly
* *(web)* Use logger directly
* *(web)* Always set internal error* Remove console.log ([40105ee](40105ee4ced980f52565baec4c3219b0ddd4f6ec))
* Fix comment ([1df4a4e](1df4a4ea2e2ca4332347468e8973a2dcbab06ed7))
* Add go and direnv to recommended vscode extensions ([6ab12b9](6ab12b9dd133b52ed7267b6e9334081c2f9719ca))
* Remove console.log ([1e7d9c9](1e7d9c982d3d472e9b4082991b41e6567556f2b2))
* Rearrange cron registers ([4857bfb](4857bfbbdb8401b6ef02b1dc8de93f2a09e8bc3a))
### Other
* *(other)* [skip ci] Updated swagger docs
## [0.24.2] - 2024-08-12
### Bug Fixes
+9 -2
View File
@@ -13,7 +13,7 @@ RUN corepack enable && \
pnpm install && \
pnpm run build
FROM --platform=$BUILDPLATFORM ghcr.io/techknowlogick/xgo:go-1.23.x AS apibuilder
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.21.x AS apibuilder
RUN go install github.com/magefile/mage@latest && \
mv /go/bin/mage /usr/local/go/bin
@@ -35,7 +35,14 @@ RUN export PATH=$PATH:$GOPATH/bin && \
# The actual image
FROM scratch
LABEL maintainer="maintainers@vikunja.io"
LABEL org.opencontainers.image.authors='maintainers@vikunja.io'
LABEL org.opencontainers.image.url='https://vikunja.io'
LABEL org.opencontainers.image.documentation='https://vikunja.io/docs'
LABEL org.opencontainers.image.source='https://code.vikunja.io/vikunja'
LABEL org.opencontainers.image.licenses='AGPLv3'
LABEL org.opencontainers.image.title='Vikunja'
WORKDIR /app/vikunja
ENTRYPOINT [ "/app/vikunja/vikunja" ]
EXPOSE 3456
+1 -1
View File
@@ -2,7 +2,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/vikunjaa/status.svg)](https://drone.kolaente.de/vikunja/vikunja)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Install](https://img.shields.io/badge/download-v0.24.6,brightgreen.svg)](https://vikunja.io/docs/installing)
[![Install](https://img.shields.io/badge/download-v0.24.2-brightgreen.svg)](https://vikunja.io/docs/installing)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/vikunja.svg)](https://hub.docker.com/r/vikunja/vikunja/)
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/vikunja)](https://goreportcard.com/report/kolaente.dev/vikunja/vikunja)
+1242 -1541
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -29,4 +29,9 @@ in {
enable = true;
};
};
services.mailpit = {
enable = true;
package = pkgs-unstable.mailpit;
};
}
@@ -148,7 +148,7 @@ describe('Project View Kanban', () => {
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .modal-header')
cy.get('.modal-mask .modal-container .modal-content .header')
.should('contain', 'Delete the bucket')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
@@ -251,7 +251,7 @@ describe('Project View Kanban', () => {
.should('be.visible')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .modal-header')
cy.get('.modal-mask .modal-container .modal-content .header')
.should('contain', 'Delete this task')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
+1 -1
View File
@@ -356,7 +356,7 @@ describe('Task', () => {
.should('be.visible')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .modal-header')
cy.get('.modal-mask .modal-container .modal-content .header')
.should('contain', 'Delete this task')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
+50 -50
View File
@@ -56,44 +56,44 @@
"@fortawesome/vue-fontawesome": "3.0.8",
"@github/hotkey": "3.1.1",
"@infectoone/vue-ganttastic": "2.3.2",
"@intlify/unplugin-vue-i18n": "5.2.0",
"@intlify/unplugin-vue-i18n": "5.0.0",
"@kyvg/vue3-notification": "3.3.0",
"@sentry/tracing": "7.114.0",
"@sentry/vue": "8.32.0",
"@tiptap/core": "2.7.4",
"@tiptap/extension-blockquote": "2.7.4",
"@tiptap/extension-bold": "2.7.4",
"@tiptap/extension-bullet-list": "2.7.4",
"@tiptap/extension-code": "2.7.4",
"@tiptap/extension-code-block": "2.7.4",
"@tiptap/extension-code-block-lowlight": "2.7.4",
"@tiptap/extension-document": "2.7.4",
"@tiptap/extension-dropcursor": "2.7.4",
"@tiptap/extension-gapcursor": "2.7.4",
"@tiptap/extension-hard-break": "2.7.4",
"@tiptap/extension-heading": "2.7.4",
"@tiptap/extension-history": "2.7.4",
"@tiptap/extension-horizontal-rule": "2.7.4",
"@tiptap/extension-image": "2.7.4",
"@tiptap/extension-italic": "2.7.4",
"@tiptap/extension-link": "2.7.4",
"@tiptap/extension-list-item": "2.7.4",
"@tiptap/extension-ordered-list": "2.7.4",
"@tiptap/extension-paragraph": "2.7.4",
"@tiptap/extension-placeholder": "2.7.4",
"@tiptap/extension-strike": "2.7.4",
"@tiptap/extension-table": "2.7.4",
"@tiptap/extension-table-cell": "2.7.4",
"@tiptap/extension-table-header": "2.7.4",
"@tiptap/extension-table-row": "2.7.4",
"@tiptap/extension-task-item": "2.7.4",
"@tiptap/extension-task-list": "2.7.4",
"@tiptap/extension-text": "2.7.4",
"@tiptap/extension-typography": "2.7.4",
"@tiptap/extension-underline": "2.7.4",
"@tiptap/pm": "2.7.4",
"@tiptap/suggestion": "2.7.4",
"@tiptap/vue-3": "2.7.4",
"@sentry/vue": "8.30.0",
"@tiptap/core": "2.7.1",
"@tiptap/extension-blockquote": "2.7.1",
"@tiptap/extension-bold": "2.7.1",
"@tiptap/extension-bullet-list": "2.7.1",
"@tiptap/extension-code": "2.7.1",
"@tiptap/extension-code-block": "2.7.1",
"@tiptap/extension-code-block-lowlight": "2.7.1",
"@tiptap/extension-document": "2.7.1",
"@tiptap/extension-dropcursor": "2.7.1",
"@tiptap/extension-gapcursor": "2.7.1",
"@tiptap/extension-hard-break": "2.7.1",
"@tiptap/extension-heading": "2.7.1",
"@tiptap/extension-history": "2.7.1",
"@tiptap/extension-horizontal-rule": "2.7.1",
"@tiptap/extension-image": "2.7.1",
"@tiptap/extension-italic": "2.7.1",
"@tiptap/extension-link": "2.7.1",
"@tiptap/extension-list-item": "2.7.1",
"@tiptap/extension-ordered-list": "2.7.1",
"@tiptap/extension-paragraph": "2.7.1",
"@tiptap/extension-placeholder": "2.7.1",
"@tiptap/extension-strike": "2.7.1",
"@tiptap/extension-table": "2.7.1",
"@tiptap/extension-table-cell": "2.7.1",
"@tiptap/extension-table-header": "2.7.1",
"@tiptap/extension-table-row": "2.7.1",
"@tiptap/extension-task-item": "2.7.1",
"@tiptap/extension-task-list": "2.7.1",
"@tiptap/extension-text": "2.7.1",
"@tiptap/extension-typography": "2.7.1",
"@tiptap/extension-underline": "2.7.1",
"@tiptap/pm": "2.7.1",
"@tiptap/suggestion": "2.7.1",
"@tiptap/vue-3": "2.7.1",
"@vueuse/core": "11.1.0",
"@vueuse/router": "11.1.0",
"axios": "1.7.7",
@@ -102,7 +102,7 @@
"change-case": "5.4.4",
"date-fns": "4.1.0",
"dayjs": "1.11.13",
"dompurify": "3.1.7",
"dompurify": "3.1.6",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.43",
@@ -113,15 +113,15 @@
"pinia": "2.2.2",
"register-service-worker": "1.7.2",
"sortablejs": "1.15.3",
"tailwindcss": "3.4.13",
"tailwindcss": "3.4.12",
"tippy.js": "6.3.7",
"ufo": "1.5.4",
"vue": "3.5.10",
"vue": "3.5.6",
"vue-advanced-cropper": "2.8.9",
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "10.0.3",
"vue-i18n": "10.0.1",
"vue-router": "4.4.5",
"vuemoji-picker": "0.3.1",
"vuemoji-picker": "0.2.1",
"workbox-precaching": "7.1.0",
"zhyswan-vuedraggable": "4.1.3"
},
@@ -137,9 +137,9 @@
"@types/codemirror": "5.60.15",
"@types/dompurify": "3.0.5",
"@types/flexsearch": "0.7.6",
"@types/is-touch-device": "1.0.3",
"@types/is-touch-device": "1.0.2",
"@types/lodash.clonedeep": "4.5.9",
"@types/node": "20.16.10",
"@types/node": "20.16.5",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
@@ -149,11 +149,11 @@
"@vue/test-utils": "2.4.6",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.20",
"browserslist": "4.24.0",
"caniuse-lite": "1.0.30001664",
"browserslist": "4.23.3",
"caniuse-lite": "1.0.30001660",
"csstype": "3.1.3",
"cypress": "13.15.0",
"esbuild": "0.24.0",
"cypress": "13.14.2",
"esbuild": "0.23.1",
"eslint": "8.57.1",
"eslint-plugin-vue": "9.28.0",
"happy-dom": "14.12.3",
@@ -162,13 +162,13 @@
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-preset-env": "9.6.0",
"rollup": "4.22.5",
"rollup": "4.21.3",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.79.4",
"sass": "1.79.1",
"start-server-and-test": "2.0.8",
"typescript": "5.6.2",
"unplugin-inject-preload": "2.0.5",
"vite": "5.4.8",
"vite": "5.4.6",
"vite-plugin-pwa": "0.20.5",
"vite-plugin-sentry": "1.4.0",
"vite-svg-loader": "5.1.0",
+814 -802
View File
File diff suppressed because it is too large Load Diff
+25 -12
View File
@@ -3,22 +3,33 @@
v-cy="'checkbox'"
class="base-checkbox"
>
<label
class="base-checkbox__label"
<input
:id="checkboxId"
type="checkbox"
class="is-sr-only"
:checked="modelValue"
:disabled="disabled || undefined"
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
>
<input
type="checkbox"
class="is-sr-only"
:checked="modelValue"
:disabled="disabled || undefined"
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
<slot
name="label"
:checkbox-id="checkboxId"
>
<label
:for="checkboxId"
class="base-checkbox__label"
>
<slot />
</label>
<slot />
</label>
</slot>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import {createRandomID} from '@/helpers/randomId'
withDefaults(defineProps<{
modelValue?: boolean,
disabled: boolean,
@@ -27,8 +38,10 @@ withDefaults(defineProps<{
})
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
(event: 'update:modelValue', value: boolean): void
}>()
const checkboxId = ref(`checkbox_${createRandomID()}`)
</script>
<style lang="scss" scoped>
@@ -40,7 +53,7 @@ const emit = defineEmits<{
}
.base-checkbox:has(input:disabled) .base-checkbox__label {
cursor: not-allowed;
cursor:not-allowed;
pointer-events: none;
}
</style>
@@ -1,97 +0,0 @@
<template>
<nav
v-if="totalPages > 1"
aria-label="pagination"
class="pagination is-centered p-4"
role="navigation"
>
<slot
name="previous"
:disabled="currentPage === 1"
>
{{ $t('misc.previous') }}
</slot>
<slot
name="next"
:disabled="currentPage === totalPages"
>
{{ $t('misc.next') }}
</slot>
<ul class="pagination-list">
<li
v-for="(p, i) in pages"
:key="`page-${i}`"
>
<span
v-if="p.isEllipsis"
class="pagination-ellipsis"
>&hellip;</span>
<slot
v-else
name="page-link"
:page="p"
:is-current="p.number === currentPage"
>
{{ p.number }}
</slot>
</li>
</ul>
</nav>
</template>
<script lang="ts" setup>
import {computed} from 'vue'
const props = defineProps<{
totalPages: number,
currentPage: number
}>()
function createPagination(totalPages: number, currentPage: number) {
const pages = []
for (let i = 0; i < totalPages; i++) {
if (
i > 0 &&
(i + 1) < totalPages &&
((i + 1) > currentPage + 1 || (i + 1) < currentPage - 1)
) {
if (pages[i - 1] && !pages[i - 1].isEllipsis) {
pages.push({
number: 0,
isEllipsis: true,
})
}
continue
}
pages.push({
number: i + 1,
isEllipsis: false,
})
}
return pages
}
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
</script>
<style lang="scss" scoped>
.pagination {
padding-bottom: 1rem;
}
.pagination-previous,
.pagination-next {
&:not(:disabled):hover {
background: $scheme-main;
cursor: pointer;
}
}
.pagination-list {
&, & li {
margin-top: 0;
}
}
</style>
@@ -38,6 +38,7 @@ const emit = defineEmits<{
}>()
</script>
<style lang="scss" scoped>
.fancy-checkbox {
display: inline-block;
@@ -69,8 +70,8 @@ const emit = defineEmits<{
}
}
.fancy-checkbox:hover input:not(:disabled) + .fancy-checkbox__icon,
.fancy-checkbox input:checked + .fancy-checkbox__icon {
.fancy-checkbox:not(:has(input:disabled)):hover .fancy-checkbox__icon,
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
--stroke-color: var(--primary);
}
</style>
@@ -79,13 +80,13 @@ const emit = defineEmits<{
// Since css-has-pseudo doesn't work with deep classes,
// the following rules can't be scoped
.fancy-checkbox :not(input:checked) + .fancy-checkbox__icon {
.fancy-checkbox:has(:not(input:checked)) .fancy-checkbox__icon {
path {
transition-delay: 0.05s;
}
}
.fancy-checkbox input:checked + .fancy-checkbox__icon {
.fancy-checkbox:has(input:checked) .fancy-checkbox__icon {
path {
stroke-dashoffset: 60;
}
+2 -10
View File
@@ -34,7 +34,7 @@
}"
>
<slot>
<div class="modal-header">
<div class="header">
<slot name="header" />
</div>
<div class="content">
@@ -140,7 +140,7 @@ $modal-width: 1024px;
transform: none;
}
.modal-header {
.header {
font-size: 2rem;
font-weight: 700;
}
@@ -241,14 +241,6 @@ $modal-width: 1024px;
margin-bottom: 0 !important;
}
}
.modal-content:has(.modal-header) {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 1rem;
min-height: 100vh
}
</style>
<style lang="scss">
+90 -34
View File
@@ -1,49 +1,89 @@
<template>
<BasePagination
:total-pages="totalPages"
:current-page="currentPage"
<nav
v-if="totalPages > 1"
aria-label="pagination"
class="pagination is-centered p-4"
role="navigation"
>
<template #previous="{ disabled }">
<RouterLink
:disabled="disabled || undefined"
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous"
<RouterLink
:disabled="currentPage === 1 || undefined"
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous"
>
{{ $t('misc.previous') }}
</RouterLink>
<RouterLink
:disabled="currentPage === totalPages || undefined"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next"
>
{{ $t('misc.next') }}
</RouterLink>
<ul class="pagination-list">
<li
v-for="(p, i) in pages"
:key="`page-${i}`"
>
{{ $t('misc.previous') }}
</RouterLink>
</template>
<template #next="{ disabled }">
<RouterLink
:disabled="disabled || undefined"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next"
>
{{ $t('misc.next') }}
</RouterLink>
</template>
<template #page-link="{ page, isCurrent }">
<RouterLink
class="pagination-link"
:aria-label="'Goto page ' + page.number"
:class="{ 'is-current': isCurrent }"
:to="getRouteForPagination(page.number)"
>
{{ page.number }}
</RouterLink>
</template>
</BasePagination>
<span
v-if="p.isEllipsis"
class="pagination-ellipsis"
>&hellip;</span>
<RouterLink
v-else
class="pagination-link"
:aria-label="'Goto page ' + p.number"
:class="{ 'is-current': p.number === currentPage }"
:to="getRouteForPagination(p.number)"
>
{{ p.number }}
</RouterLink>
</li>
</ul>
</nav>
</template>
<script lang="ts" setup>
import BasePagination from '@/components/base/BasePagination.vue'
import {computed} from 'vue'
withDefaults(defineProps<{
const props = withDefaults(defineProps<{
totalPages: number,
currentPage?: number
}>(), {
currentPage: 0,
})
function createPagination(totalPages: number, currentPage: number) {
const pages = []
for (let i = 0; i < totalPages; i++) {
// Show ellipsis instead of all pages
if (
i > 0 && // Always at least the first page
(i + 1) < totalPages && // And the last page
(
// And the current with current + 1 and current - 1
(i + 1) > currentPage + 1 ||
(i + 1) < currentPage - 1
)
) {
// Only add an ellipsis if the last page isn't already one
if (pages[i - 1] && !pages[i - 1].isEllipsis) {
pages.push({
number: 0,
isEllipsis: true,
})
}
continue
}
pages.push({
number: i + 1,
isEllipsis: false,
})
}
return pages
}
function getRouteForPagination(page = 1, type = null) {
return {
name: type,
@@ -55,4 +95,20 @@ function getRouteForPagination(page = 1, type = null) {
},
}
}
</script>
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
</script>
<style lang="scss" scoped>
.pagination {
padding-bottom: 1rem;
}
.pagination-previous,
.pagination-next {
&:not(:disabled):hover {
background: $scheme-main;
cursor: pointer;
}
}
</style>
@@ -1,55 +0,0 @@
<template>
<BasePagination
:total-pages="totalPages"
:current-page="currentPage"
>
<template #previous="{ disabled }">
<BaseButton
:disabled="disabled"
class="pagination-previous"
@click="changePage(currentPage - 1)"
>
{{ $t('misc.previous') }}
</BaseButton>
</template>
<template #next="{ disabled }">
<BaseButton
:disabled="disabled"
class="pagination-next"
@click="changePage(currentPage + 1)"
>
{{ $t('misc.next') }}
</BaseButton>
</template>
<template #page-link="{ page, isCurrent }">
<BaseButton
class="pagination-link"
:aria-label="'Goto page ' + page.number"
:class="{ 'is-current': isCurrent }"
@click="changePage(page.number)"
>
{{ page.number }}
</BaseButton>
</template>
</BasePagination>
</template>
<script lang="ts" setup>
import BasePagination from '@/components/base/BasePagination.vue'
import BaseButton from '@/components/base/BaseButton.vue'
const props = withDefaults(defineProps<{
totalPages: number,
currentPage: number
}>(), {
currentPage: 1,
})
const emit = defineEmits(['pageChanged'])
function changePage(page: number) {
if (page >= 1 && page <= props.totalPages) {
emit('pageChanged', page)
}
}
</script>
@@ -569,15 +569,15 @@ async function addTaskToBucket(bucketId: IBucket['id']) {
toggleShowNewTaskInput(bucketId)
newTaskText.value = ''
kanbanStore.addTaskToBucket(task)
scrollTaskContainerToBottom(bucketId)
scrollTaskContainerToTop(bucketId)
}
function scrollTaskContainerToBottom(bucketId: IBucket['id']) {
function scrollTaskContainerToTop(bucketId: IBucket['id']) {
const bucketEl = taskContainerRefs.value[bucketId]
if (!bucketEl) {
return
}
bucketEl.scrollTop = bucketEl.scrollHeight
bucketEl.scrollTop = 0
}
async function createNewBucket() {
@@ -180,7 +180,7 @@ import ProgressBar from '@/components/misc/ProgressBar.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import AttachmentService from '@/services/attachment'
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
import {canPreview} from '@/models/attachment'
import type {IAttachment} from '@/modelTypes/IAttachment'
import type {ITask} from '@/modelTypes/ITask'
@@ -274,10 +274,6 @@ async function viewOrDownload(attachment: IAttachment) {
}
}
function canPreview(attachment: IAttachment): boolean {
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
}
const copy = useCopyToClipboard()
function copyUrl(attachment: IAttachment) {
@@ -1,8 +1,7 @@
<template>
<div
v-if="enabled"
ref="commentsRef"
class="content details comments-container"
class="content details"
>
<h3
v-if="canWrite || comments.length > 0"
@@ -16,13 +15,14 @@
<div class="comments">
<span
v-if="taskCommentService.loading && saving === null && !creating"
class="is-flex is-align-items-center my-4 ml-2"
class="is-inline-flex is-align-items-center"
>
<span class="loader is-inline-block mr-2" />
{{ $t('task.comment.loading') }}
</span>
<div
v-for="c in comments"
:id="`comment-${c.id}`"
:key="c.id"
class="media comment"
>
@@ -57,6 +57,13 @@
>
· {{ $t('task.comment.edited', {date: formatDateSince(c.updated)}) }}
</span>
<a
v-tooltip="$t('task.comment.permalink')"
:href="`#comment-${c.id}`"
class="comment-permalink"
:title="$t('task.comment.permalink')"
@click.prevent.stop="copy(getCommentUrl(`${c.id}`))"
><Icon icon="link" /></a>
<CustomTransition name="fade">
<span
v-if="
@@ -108,14 +115,6 @@
/>
</div>
</div>
<PaginationEmit
v-if="taskCommentService.totalPages > 1"
:total-pages="taskCommentService.totalPages"
:current-page="currentPage"
@pageChanged="changePage"
/>
<div
v-if="canWrite"
class="media comment d-print-none"
@@ -169,7 +168,6 @@
</div>
</div>
<Modal
:enabled="showDeleteModal"
@close="showDeleteModal = false"
@@ -195,7 +193,6 @@ import {useI18n} from 'vue-i18n'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import Editor from '@/components/input/AsyncEditor'
import PaginationEmit from '@/components/misc/PaginationEmit.vue'
import TaskCommentService from '@/services/taskComment'
import TaskCommentModel from '@/models/taskComment'
@@ -210,6 +207,7 @@ import {getAvatarUrl, getDisplayName} from '@/models/user'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
import Reactions from '@/components/input/Reactions.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
const props = withDefaults(defineProps<{
taskId: number,
@@ -218,6 +216,8 @@ const props = withDefaults(defineProps<{
canWrite: true,
})
const copy = useCopyToClipboard()
const {t} = useI18n({useScope: 'global'})
const configStore = useConfigStore()
const authStore = useAuthStore()
@@ -255,10 +255,6 @@ const actions = computed(() => {
const frontendUrl = computed(() => configStore.frontendUrl)
const currentPage = ref(1)
const commentsRef = ref<HTMLElement | null>(null)
async function attachmentUpload(files: File[] | FileList): (Promise<string[]>) {
const uploadPromises: Promise<string>[] = []
@@ -284,21 +280,12 @@ async function loadComments(taskId: ITask['id']) {
newComment.taskId = taskId
commentEdit.taskId = taskId
commentToDelete.taskId = taskId
comments.value = await taskCommentService.getAll({taskId}, {}, currentPage.value)
}
async function changePage(page: number) {
commentsRef.value?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' })
currentPage.value = page
await loadComments(props.taskId)
comments.value = await taskCommentService.getAll({taskId})
}
watch(
() => props.taskId,
() => {
currentPage.value = 1 // Reset to first page when task changes
loadComments(props.taskId)
},
loadComments,
{immediate: true},
)
@@ -390,6 +377,10 @@ async function deleteComment(commentToDelete: ITaskComment) {
showDeleteModal.value = false
}
}
function getCommentUrl(commentId: string) {
return `${frontendUrl.value}${location.pathname}${location.search}#comment-${commentId}`
}
</script>
<style lang="scss" scoped>
@@ -417,10 +408,23 @@ async function deleteComment(commentToDelete: ITaskComment) {
}
span {
span,
.comment-permalink {
font-size: .75rem;
line-height: 1;
}
.comment-permalink {
font-size: 1rem;
border: 1px solid transparent;
padding: 0.25rem;
border-radius: 1rem;
color: var(--grey, hsl(0, 0%, 48%));
}
.comment-permalink:hover {
color: var(--grey-dark, hsl(0, 0%, 29%));
border-color: var(--grey-dark, hsl(0, 0%, 29%));
}
}
.image.is-avatar {
@@ -430,8 +434,4 @@ async function deleteComment(commentToDelete: ITaskComment) {
.media-content {
width: calc(100% - 48px - 2rem);
}
.comments-container {
scroll-margin-top: 4rem;
}
</style>
@@ -20,9 +20,9 @@
<script setup lang="ts">
import {ref, shallowReactive, watchEffect} from 'vue'
import AttachmentService from '@/services/attachment'
import AttachmentService, {PREVIEW_SIZE} from '@/services/attachment'
import type {IAttachment} from '@/modelTypes/IAttachment'
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
import {canPreview} from '@/models/attachment'
const props = defineProps<{
modelValue?: IAttachment
@@ -33,13 +33,9 @@ const blobUrl = ref<string | undefined>(undefined)
watchEffect(async () => {
if (props.modelValue && canPreview(props.modelValue)) {
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue)
blobUrl.value = await attachmentService.getBlobUrl(props.modelValue, PREVIEW_SIZE.MD)
}
})
function canPreview(attachment: IAttachment): boolean {
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
}
</script>
<style scoped lang="scss">
@@ -16,7 +16,6 @@
:is-done="task.done"
/>
<BaseButton
v-if="hasClose"
class="close"
@click="$emit('close')"
>
@@ -34,7 +33,6 @@
{{ task.title.trim() }}
</h1>
<BaseButton
v-if="hasClose"
class="close"
@click="$emit('close')"
>
@@ -80,10 +78,8 @@ import {getHexColor, getTaskIdentifier} from '@/models/task'
const props = withDefaults(defineProps<{
task: ITask,
canWrite: boolean | undefined,
hasClose: boolean | undefined,
}>(), {
canWrite: false,
hasClose: false,
})
const emit = defineEmits<{
@@ -93,7 +93,7 @@
</template>
<script lang="ts" setup>
import {ref, computed, watch} from 'vue'
import {computed, ref, watch} from 'vue'
import {useRouter} from 'vue-router'
import PriorityLabel from '@/components/tasks/partials/PriorityLabel.vue'
@@ -105,9 +105,9 @@ import ChecklistSummary from './ChecklistSummary.vue'
import {getHexColor} from '@/models/task'
import type {ITask} from '@/modelTypes/ITask'
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
import AttachmentService from '@/services/attachment'
import AttachmentService, {PREVIEW_SIZE} from '@/services/attachment'
import {formatDateLong, formatISO, formatDateSince} from '@/helpers/time/formatDate'
import {formatDateLong, formatDateSince, formatISO} from '@/helpers/time/formatDate'
import {colorIsDark} from '@/helpers/color/colorIsDark'
import {useTaskStore} from '@/stores/tasks'
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
@@ -165,7 +165,7 @@ async function maybeDownloadCoverImage() {
}
const attachmentService = new AttachmentService()
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment)
coverImageBlobUrl.value = await attachmentService.getBlobUrl(attachment, PREVIEW_SIZE.LG)
}
watch(
@@ -91,9 +91,8 @@
</time>
</BaseButton>
</template>
<template #content="{isOpen}">
<template #content>
<DeferTask
v-if="isOpen"
v-model="task"
/>
</template>
@@ -353,7 +352,7 @@ function hasTextSelected() {
function openTaskDetail(event: MouseEvent | KeyboardEvent) {
if (event.target instanceof HTMLElement) {
const isInteractiveElement = event.target.closest('a, button, .favorite, [role="button"]')
const isInteractiveElement = event.target.closest('a, button, input, .favorite, [role="button"]')
if (isInteractiveElement || hasTextSelected()) {
return
}
@@ -378,7 +377,7 @@ function openTaskDetail(event: MouseEvent | KeyboardEvent) {
background-color: var(--grey-100);
}
&:has(*:focus-visible) {
&:focus-within {
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
a.task-link {
@@ -388,7 +387,7 @@ function openTaskDetail(event: MouseEvent | KeyboardEvent) {
@supports not selector(:focus-within) {
:focus {
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
a.task-link {
box-shadow: none;
@@ -412,15 +411,6 @@ function openTaskDetail(event: MouseEvent | KeyboardEvent) {
.dueDate {
display: inline-block;
margin-left: 5px;
&:focus-visible {
box-shadow: none;
time {
box-shadow: 0 0 0 1px hsla(var(--primary-hsl), 0.5);
border-radius: 3px;
}
}
}
.overdue {
@@ -466,7 +456,6 @@ function openTaskDetail(event: MouseEvent | KeyboardEvent) {
text-align: center;
width: 27px;
transition: opacity $transition, color $transition;
border-radius: $radius;
&:hover {
color: var(--warning);
+1
View File
@@ -25,6 +25,7 @@ export const SUPPORTED_LOCALES = {
'pt-BR': 'Português Brasileiro',
'hr-HR': 'Hrvatski',
'uk-UA': 'Українська',
'lt-LT': 'Lietuvių Kalba',
// IMPORTANT: Also add new languages to useDayjsLanguageSync
} as const
+2 -1
View File
@@ -848,7 +848,8 @@
"delete": "Delete this comment",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteSuccess": "The comment was deleted successfully.",
"addedSuccess": "The comment was added successfully."
"addedSuccess": "The comment was added successfully.",
"permalink": "Copy permalink to this comment"
},
"deferDueDate": {
"title": "Defer due date",
@@ -25,6 +25,7 @@ export const DAYJS_LOCALE_MAPPING = {
'pt-BR': 'pt',
'hr-HR': 'hr',
'uk-UA': 'uk',
'lt-LT': 'lt',
} as Record<SupportedLocale, ISOLanguage>
export const DAYJS_LANGUAGE_IMPORTS = {
@@ -48,6 +49,7 @@ export const DAYJS_LANGUAGE_IMPORTS = {
'sl-si': () => import('dayjs/locale/sl'),
'pt-br': () => import('dayjs/locale/pt-br'),
'uk-ua': () => import('dayjs/locale/uk'),
'lt-lt': () => import('dayjs/locale/lt'),
} as Record<SupportedLocale, () => Promise<ILocale>>
export function useDayjsLanguageSync(dayjsGlobal: typeof dayjs) {
+4
View File
@@ -7,6 +7,10 @@ import type { IAttachment } from '@/modelTypes/IAttachment'
export const SUPPORTED_IMAGE_SUFFIX = ['.jpg', '.png', '.bmp', '.gif']
export function canPreview(attachment: IAttachment): boolean {
return SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.toLowerCase().endsWith(suffix))
}
export default class AttachmentModel extends AbstractModel<IAttachment> implements IAttachment {
id = 0
taskId = 0
+14 -2
View File
@@ -5,6 +5,13 @@ import type { IAttachment } from '@/modelTypes/IAttachment'
import {downloadBlob} from '@/helpers/downloadBlob'
export enum PREVIEW_SIZE {
SM = 'sm',
MD = 'md',
LG = 'lg',
XL = 'xl',
}
export default class AttachmentService extends AbstractService<IAttachment> {
constructor() {
super({
@@ -37,8 +44,13 @@ export default class AttachmentService extends AbstractService<IAttachment> {
return data
}
getBlobUrl(model: IAttachment) {
return AbstractService.prototype.getBlobUrl.call(this, '/tasks/' + model.taskId + '/attachments/' + model.id)
getBlobUrl(model: IAttachment, size?: PREVIEW_SIZE) {
let mainUrl = '/tasks/' + model.taskId + '/attachments/' + model.id
if (size !== undefined) {
mainUrl += `?preview_size=${size}`
}
return AbstractService.prototype.getBlobUrl.call(this, mainUrl)
}
async download(model: IAttachment) {
+1 -1
View File
@@ -194,8 +194,8 @@ export const useKanbanStore = defineStore('kanban', () => {
...oldBucket,
count: (oldBucket?.count || 0) + 1,
tasks: [
...oldBucket.tasks,
task,
...oldBucket.tasks,
],
}
buckets.value[bucketIndex] = newBucket
+2 -5
View File
@@ -15,7 +15,6 @@
ref="heading"
:task="task"
:can-write="canWrite"
:has-close="isModal"
@update:task="Object.assign(task, $event)"
@close="$emit('close')"
/>
@@ -573,10 +572,8 @@
</template>
<template #text>
<p class="tw-text-balance !tw-mb-0">
{{ $t('task.detail.delete.text1') }}
</p>
<p class="tw-text-balance">
<p>
{{ $t('task.detail.delete.text1') }}<br>
{{ $t('task.detail.delete.text2') }}
</p>
</template>
+2 -1
View File
@@ -64,6 +64,7 @@ require (
github.com/stretchr/testify v1.9.0
github.com/swaggo/swag v1.16.3
github.com/tkuchiki/go-timezone v0.2.3
github.com/typesense/typesense-go v1.1.0
github.com/typesense/typesense-go/v2 v2.0.0
github.com/ulule/limiter/v3 v3.11.2
github.com/wneessen/go-mail v0.4.4
@@ -78,7 +79,7 @@ require (
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.5.0
src.techknowlogick.com/xgo v1.8.1-0.20241105013731-313dedef864f
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884
src.techknowlogick.com/xormigrate v1.7.1
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.9
+52 -5
View File
@@ -1,3 +1,5 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
@@ -19,6 +21,8 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
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/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o=
github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to=
github.com/adlio/trello v1.12.0 h1:JqOE2GFHQ9YtEviRRRSnicSxPbt4WFOxhqXzjMOw8lw=
@@ -50,6 +54,8 @@ github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXye
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
@@ -91,12 +97,18 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
github.com/getsentry/sentry-go v0.29.0 h1:YtWluuCFg9OfcqnaujpY918N/AhCCwarIDWOYSBAjCA=
github.com/getsentry/sentry-go v0.29.0/go.mod h1:jhPesDAL0Q0W2+2YEuVOvdWmVtdsr1+jtBrlDEVWwLY=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -270,6 +282,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible h1:q7DbyV+sFjEoTuuUdRDNl2nlyfztkZgxVVCV7JhzIkY=
@@ -284,8 +298,6 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c=
github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
@@ -329,6 +341,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
@@ -385,14 +399,28 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
@@ -426,6 +454,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -471,7 +501,8 @@ github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
github.com/tkuchiki/go-timezone v0.2.3 h1:D3TVdIPrFsu9lxGxqNX2wsZwn1MZtTqTW0mdevMozHc=
github.com/tkuchiki/go-timezone v0.2.3/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY=
github.com/typesense/typesense-go/v2 v2.0.0 h1:+MksOnrVioDqsGpz8RXkOUqhVN+yFxZwJlGDQHr/64I=
github.com/typesense/typesense-go v1.1.0 h1:QocehDarVXRArMIosPIdawiVFZZbnRkPJxwnAGOFkzw=
github.com/typesense/typesense-go v1.1.0/go.mod h1:KcPODU7ltrcUFC/gygMTkAAfZ9M8/q6ayrdl1MnE1kI=
github.com/typesense/typesense-go/v2 v2.0.0/go.mod h1:7V1ZBSfmdciL6yb2bPtWha+W53gV5WZhyOSpVgDJfao=
github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA=
github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI=
@@ -529,11 +560,15 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -561,6 +596,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -569,6 +606,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -598,12 +637,16 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -613,6 +656,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -650,6 +695,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -723,8 +770,8 @@ mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
src.techknowlogick.com/xgo v1.8.1-0.20241105013731-313dedef864f h1:Dy7qQ31o3z4EV+0ISDH1IldkPxzubOAvV7DW+9HnbNg=
src.techknowlogick.com/xgo v1.8.1-0.20241105013731-313dedef864f/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884 h1:Ttvt8FCpUXfC8r3+LgSPrBUIr/JkHmYQtvmOwEET8qE=
src.techknowlogick.com/xgo v1.7.1-0.20240403232151-e01c4fbef884/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xormigrate v1.7.1 h1:RKGLLUAqJ+zO8iZ7eOc7oLH7f0cs2gfXSZSvBRBHnlY=
src.techknowlogick.com/xormigrate v1.7.1/go.mod h1:YGNBdj8prENlySwIKmfoEXp7ILGjAltyKFXD0qLgD7U=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+21 -13
View File
@@ -464,6 +464,26 @@ func (Build) Build() {
runAndStreamOutput("go", "build", Goflags[0], "-tags", Tags, "-ldflags", "-s -w "+Ldflags, "-o", Executable)
}
func (Build) SaveVersionToFile() error {
// Open the file for writing. If the file doesn't exist, create it.
// If it exists, truncate it.
file, err := os.Create("VERSION")
if err != nil {
return fmt.Errorf("error creating VERSION file: %w", err)
}
defer file.Close()
// Write the version number to the file
_, err = file.WriteString(VersionNumber)
if err != nil {
return fmt.Errorf("error writing to VERSION file: %w", err)
}
fmt.Println("Version number saved successfully to VERSION file")
return nil
}
type Release mg.Namespace
// Runs all steps in the right order to create release packages for various platforms
@@ -547,19 +567,7 @@ func (Release) Windows() error {
// Builds binaries for linux
func (Release) Linux() error {
targets := []string{
"linux/amd64",
"linux/arm-5",
"linux/arm-6",
"linux/arm-7",
"linux/arm64",
"linux/mips",
"linux/mipsle",
"linux/mips64",
"linux/mips64le",
"linux/riscv64",
}
return runXgo(strings.Join(targets, ","))
return runXgo("linux/*")
}
// Builds binaries for darwin
+16 -1
View File
@@ -17,11 +17,14 @@
package cmd
import (
"path/filepath"
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/dump"
"github.com/spf13/cobra"
)
@@ -29,6 +32,8 @@ func init() {
rootCmd.AddCommand(dumpCmd)
}
var dumpPathFlag string
var dumpCmd = &cobra.Command{
Use: "dump",
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
@@ -37,8 +42,18 @@ var dumpCmd = &cobra.Command{
},
Run: func(_ *cobra.Command, _ []string) {
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
if err := dump.Dump(filename); err != nil {
path := config.ServiceRootpath.GetString()
if dumpPathFlag != "" {
path = dumpPathFlag
}
if err := dump.Dump(filepath.Join(path, filename)); err != nil {
log.Critical(err.Error())
}
},
}
func init() {
dumpCmd.Flags().StringVarP(&dumpPathFlag, "path", "p", "", "The folder path where the dump file should be saved. Vikunja will use the configured root path or the binary location if the flag is not provided.")
}
+1 -6
View File
@@ -22,7 +22,6 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"time"
"code.vikunja.io/api/pkg/config"
@@ -59,12 +58,8 @@ func (*File) TableName() string {
}
func (f *File) getAbsoluteFilePath() string {
base := ""
if !strings.HasPrefix(config.FilesBasePath.GetString(), "/") {
base = config.ServiceRootpath.GetString()
}
return filepath.Join(
base,
config.ServiceRootpath.GetString(),
config.FilesBasePath.GetString(),
strconv.FormatInt(f.ID, 10),
)
+3 -3
View File
@@ -26,13 +26,13 @@ import (
)
// ErrFmt holds the format for all the console logging
const ErrFmt = `${time_rfc3339}: ${level} ` + "\t" + `▶ ${prefix} ${short_file}:${line}`
const ErrFmt = `${time_rfc3339_nano}: ${level} ` + "\t" + `▶ ${prefix} ${short_file}:${line}`
// WebFmt holds the format for all logging related to web requests
const WebFmt = `${time_rfc3339}: WEB ` + "\t" + `▶ ${remote_ip} ${id} ${method} ${status} ${uri} ${latency_human} - ${user_agent}`
const WebFmt = `${time_rfc3339_nano}: WEB ` + "\t" + `▶ ${remote_ip} ${id} ${method} ${status} ${uri} ${latency_human} - ${user_agent}`
// Fmt is the general log format
const Fmt = `%{color}%{time:` + time.RFC3339 + `}: %{level}` + "\t" + `▶ %{id:03x}%{color:reset} %{message}`
const Fmt = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ %{shortpkg}/%{shortfunc} %{id:03x}%{color:reset} %{message}`
const logModule = `vikunja`
-90
View File
@@ -1,90 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package migration
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
type projectView20240919130957BucketConfiguration struct {
Title string `json:"title"`
Filter string `json:"filter"`
}
type projectView20240919130957Lowercase struct {
ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view"`
BucketConfiguration []*projectView20240919130957BucketConfiguration `xorm:"json" json:"bucket_configuration"`
}
func (projectView20240919130957Lowercase) TableName() string {
return "project_views"
}
type projectView20240919130957TitleCase struct {
ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view"`
BucketConfiguration []*struct {
Title string `json:"Title"`
Filter string `json:"Filter"`
} `xorm:"json" json:"bucket_configuration"`
}
func (projectView20240919130957TitleCase) TableName() string {
return "project_views"
}
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20240919130957",
Description: "",
Migrate: func(tx *xorm.Engine) (err error) {
oldViews := []*projectView20240919130957TitleCase{}
// 1 is manual
err = tx.Where("bucket_configuration_mode != 1 AND view_kind = 3").Find(&oldViews)
if err != nil {
return
}
if len(oldViews) == 0 {
return nil
}
for _, view := range oldViews {
newView := &projectView20240919130957Lowercase{
ID: view.ID,
BucketConfiguration: make([]*projectView20240919130957BucketConfiguration, 0),
}
for _, bc := range view.BucketConfiguration {
newView.BucketConfiguration = append(newView.BucketConfiguration, &projectView20240919130957BucketConfiguration{
Filter: bc.Filter,
Title: bc.Title,
})
}
_, err = tx.Where("id = ?", view.ID).Update(newView)
if err != nil {
return
}
}
return
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}
+1 -5
View File
@@ -36,7 +36,6 @@ import (
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
"code.vikunja.io/api/pkg/version"
"xorm.io/xorm"
)
@@ -107,10 +106,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
// Save the file id with the user
u.ExportFileID = exportFile.ID
_, err = s.
Where("id = ?", u.ID).
Cols("export_file_id").
Update(u)
_, err = s.Cols("export_file_id").Update(u)
if err != nil {
return
}
+48 -8
View File
@@ -533,10 +533,55 @@ func (l *AddTaskToTypesense) Handle(msg *message.Message) (err error) {
s := db.NewSession()
defer s.Close()
task := make(map[int64]*Task, 1)
task[event.Task.ID] = event.Task // Will be filled with all data by the Typesense connector
positionsMap, err := getPositionsForTask(s, event)
if err != nil {
return err
}
return reindexTasksInTypesense(s, task)
bucketsMap, err := getBucketsForTask(s, event)
if err != nil {
return err
}
ttask, err := getTypesenseTaskForTask(s, event.Task, nil, positionsMap, bucketsMap)
if err != nil {
return err
}
_, err = typesenseClient.Collection("tasks").
Documents().
Create(context.Background(), ttask)
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
@@ -727,11 +772,6 @@ func (l *UpdateTaskInSavedFilterViews) Handle(msg *message.Message) (err error)
if err != nil {
return
}
task := make(map[int64]*Task, 1)
task[event.Task.ID] = event.Task // Will be filled with all data by the Typesense connector
return reindexTasksInTypesense(s, task)
}
return nil
+24 -61
View File
@@ -19,8 +19,6 @@ package models
import (
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/cron"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
@@ -360,13 +358,8 @@ func RegisterAddTaskToFilterViewCron() {
defer s.Close()
// Get all filters with a date clause and a manual kanban view
where := "filters LIKE '%_date%'"
if db.GetDialect() == builder.POSTGRES {
where = "filters::jsonb ?| array['due_date', 'start_date', 'end_date']"
}
filters := map[int64]*SavedFilter{}
err := s.Where(where).Find(&filters)
err := s.Where("filters LIKE '%_date%'").Find(&filters)
if err != nil {
log.Errorf("%sError fetching filters: %s", logPrefix, err)
return
@@ -403,7 +396,6 @@ func RegisterAddTaskToFilterViewCron() {
newTaskBuckets := []*TaskBucket{}
newTaskPositions := []*TaskPosition{}
deleteCond := []builder.Cond{}
taskIDsToRemove := []int64{}
for _, view := range kanbanFilterViews {
filterID := getSavedFilterIDFromProjectID(view.ProjectID)
filter, exists := filters[filterID]
@@ -488,64 +480,35 @@ func RegisterAddTaskToFilterViewCron() {
builder.Eq{"task_id": taskID},
builder.Eq{"project_view_id": view.ID},
))
taskIDsToRemove = append(taskIDsToRemove, taskID)
}
}
}
upsertRelatedTaskProperties(s, logPrefix, newTaskBuckets, newTaskPositions, deleteCond, taskIDsToRemove)
if len(newTaskBuckets) > 0 {
_, err = s.Insert(newTaskBuckets)
if err != nil {
log.Errorf("%sError inserting task buckets: %s", logPrefix, err)
}
}
if len(newTaskPositions) > 0 {
_, err = s.Insert(newTaskPositions)
if err != nil {
log.Errorf("%sError inserting task positions: %s", logPrefix, err)
}
}
if len(deleteCond) > 0 {
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskBucket{})
if err != nil {
log.Errorf("%sError deleting task buckets: %s", logPrefix, err)
}
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskPosition{})
if err != nil {
log.Errorf("%sError deleting task positions: %s", logPrefix, err)
}
}
})
if err != nil {
log.Fatalf("Could register add task to filter view cron: %s", err)
}
}
func upsertRelatedTaskProperties(s *xorm.Session, logPrefix string, newTaskBuckets []*TaskBucket, newTaskPositions []*TaskPosition, deleteCond []builder.Cond, taskIDsToRemove []int64) {
var err error
if len(newTaskBuckets) > 0 {
_, err = s.Insert(newTaskBuckets)
if err != nil {
log.Errorf("%sError inserting task buckets: %s", logPrefix, err)
}
}
if len(newTaskPositions) > 0 {
_, err = s.Insert(newTaskPositions)
if err != nil {
log.Errorf("%sError inserting task positions: %s", logPrefix, err)
}
}
if len(deleteCond) > 0 {
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskBucket{})
if err != nil {
log.Errorf("%sError deleting task buckets: %s", logPrefix, err)
}
_, err = s.Where(builder.Or(deleteCond...)).Delete(&TaskPosition{})
if err != nil {
log.Errorf("%sError deleting task positions: %s", logPrefix, err)
}
}
if config.TypesenseEnabled.GetBool() && (len(newTaskPositions) > 0 || len(taskIDsToRemove) > 0) {
taskIDs := []int64{}
for _, position := range newTaskPositions {
taskIDs = append(taskIDs, position.TaskID)
}
taskIDs = append(taskIDs, taskIDsToRemove...)
tasks, err := GetTasksSimpleByIDs(s, taskIDs)
if err != nil {
log.Errorf("%sError fetching tasks: %s", logPrefix, err)
return
}
taskMap := make(map[int64]*Task)
for _, t := range tasks {
taskMap[t.ID] = t
}
err = reindexTasksInTypesense(s, taskMap)
if err != nil {
log.Errorf("%sError reindexing tasks into Typesense: %s", logPrefix, err)
return
}
}
}
+113 -11
View File
@@ -17,14 +17,21 @@
package models
import (
"bytes"
"image"
"image/png"
"io"
"strconv"
"time"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/keyvalue"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web"
"github.com/disintegration/imaging"
"xorm.io/xorm"
)
@@ -132,16 +139,7 @@ func (ta *TaskAttachment) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
// @Failure 404 {object} models.Message "The task does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{id}/attachments [get]
func (ta *TaskAttachment) ReadAll(s *xorm.Session, a web.Auth, _ string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
task := Task{ID: ta.TaskID}
canRead, _, err := task.CanRead(s, a)
if err != nil {
return nil, 0, 0, err
}
if !canRead {
return nil, 0, 0, ErrGenericForbidden{}
}
func (ta *TaskAttachment) ReadAll(s *xorm.Session, _ web.Auth, _ string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
attachments := []*TaskAttachment{}
limit, start := getLimitFromPageIndex(page, perPage)
@@ -194,6 +192,110 @@ func (ta *TaskAttachment) ReadAll(s *xorm.Session, a web.Auth, _ string, page in
return attachments, len(attachments), numberOfTotalItems, err
}
func cacheKeyForTaskAttachmentPreview(id int64, size PreviewSize) string {
return "task_attachment_preview_" + strconv.FormatInt(id, 10) + "_size_" + string(size)
}
func (ta *TaskAttachment) GetPreviewFromCache(previewSize PreviewSize) []byte {
cacheKey := cacheKeyForTaskAttachmentPreview(ta.ID, previewSize)
var cached []byte
exists, err := keyvalue.GetWithValue(cacheKey, &cached)
// If the preview is not cached, return nil
if err != nil || !exists || cached == nil {
return nil
}
return cached
}
type PreviewSize string
const (
PreviewSizeUnknown PreviewSize = "unknown"
PreviewSmall PreviewSize = "sm"
PreviewMedium PreviewSize = "md"
PreviewLarge PreviewSize = "lg"
PreviewExtraLarge PreviewSize = "xl"
)
func (previewSize PreviewSize) GetSize() int {
switch previewSize {
case PreviewSmall:
return 100
case PreviewMedium:
return 200
case PreviewLarge:
return 400
case PreviewExtraLarge:
return 800
case PreviewSizeUnknown:
return 0
default:
return 200
}
}
func GetPreviewSizeFromString(size string) PreviewSize {
switch size {
case "sm":
return PreviewSmall
case "md":
return PreviewMedium
case "lg":
return PreviewLarge
case "xl":
return PreviewExtraLarge
}
return PreviewSizeUnknown
}
func resizeImage(img image.Image, width int) *image.NRGBA {
resizedImg := imaging.Resize(img, width, 0, imaging.Lanczos)
log.Debugf(
"Resized attachment image from %vx%v to %vx%v for a preview",
img.Bounds().Size().X,
img.Bounds().Size().Y,
resizedImg.Bounds().Size().X,
resizedImg.Bounds().Size().Y,
)
return resizedImg
}
func (ta *TaskAttachment) GenerateAndSavePreviewToCache(previewSize PreviewSize) []byte {
img, _, err := image.Decode(ta.File.File)
if err != nil {
return nil
}
// Scale down the image to a minimum size
resizedImg := resizeImage(img, previewSize.GetSize())
// Get the raw bytes of the resized image
buf := &bytes.Buffer{}
if err := png.Encode(buf, resizedImg); err != nil {
return nil
}
previewImage, err := io.ReadAll(buf)
if err != nil {
return nil
}
// Store the preview image in the cache
cacheKey := cacheKeyForTaskAttachmentPreview(ta.ID, previewSize)
err = keyvalue.Put(cacheKey, previewImage)
if err != nil {
return nil
}
log.Infof("Attachment image preview for task attachment %v of size %v created and cached", ta.ID, previewSize)
return previewImage
}
// Delete removes an attachment
// @Summary Delete an attachment
// @Description Delete an attachment.
-8
View File
@@ -233,14 +233,6 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
return nil, 0, 0, err
}
canRead, _, err := sf.CanRead(s, a)
if err != nil {
return nil, 0, 0, err
}
if !canRead {
return nil, 0, 0, ErrGenericForbidden{}
}
// By prepending sort options before the saved ones from the filter, we make sure the supplied sort
// options via query take precedence over the rest.
+36
View File
@@ -181,3 +181,39 @@ func getPositionsForView(s *xorm.Session, view *ProjectView) (positions []*TaskP
Find(&positions)
return
}
func calculateNewPositionForTask(s *xorm.Session, a web.Auth, t *Task, view *ProjectView) (*TaskPosition, error) {
if t.Position == 0 {
lowestPosition := &TaskPosition{}
exists, err := s.Where("project_view_id = ?", view.ID).
OrderBy("position asc").
Get(lowestPosition)
if err != nil {
return nil, err
}
if exists {
if lowestPosition.Position == 0 {
err = RecalculateTaskPositions(s, view, a)
if err != nil {
return nil, err
}
lowestPosition = &TaskPosition{}
_, err = s.Where("project_view_id = ?", view.ID).
OrderBy("position asc").
Get(lowestPosition)
if err != nil {
return nil, err
}
}
t.Position = lowestPosition.Position / 2
}
}
return &TaskPosition{
TaskID: t.ID,
ProjectViewID: view.ID,
Position: calculateDefaultPosition(t.Index, t.Position),
}, nil
}
+4 -4
View File
@@ -25,9 +25,9 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/web"
"github.com/typesense/typesense-go/typesense/api"
"github.com/typesense/typesense-go/typesense/api/pointer"
"github.com/typesense/typesense-go/v2/typesense/api"
"github.com/typesense/typesense-go/v2/typesense/api/pointer"
"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
@@ -565,8 +565,8 @@ func (t *typesenseTaskSearcher) Search(opts *taskSearchOptions) (tasks []*Task,
}
params := &api.SearchCollectionParams{
Q: pointer.String(opts.search),
QueryBy: pointer.String("title, identifier, description, comments.comment"),
Q: opts.search,
QueryBy: "title, identifier, description, comments.comment",
Page: pointer.Int(opts.page),
ExhaustiveSearch: pointer.True(),
FilterBy: pointer.String(strings.Join(filterBy, " && ")),
+11 -10
View File
@@ -36,7 +36,7 @@ import (
"github.com/google/uuid"
clone "github.com/huandu/go-clone/generic"
"github.com/jinzhu/copier"
"github.com/typesense/typesense-go/v2/typesense"
"github.com/typesense/typesense-go/typesense"
"xorm.io/builder"
"xorm.io/xorm"
)
@@ -812,11 +812,12 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool, setB
})
}
positions = append(positions, &TaskPosition{
TaskID: t.ID,
ProjectViewID: view.ID,
Position: calculateDefaultPosition(t.Index, t.Position),
})
newPosition, err := calculateNewPositionForTask(s, a, t, view)
if err != nil {
return err
}
positions = append(positions, newPosition)
}
if len(positions) > 0 {
@@ -976,11 +977,11 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
return err
}
tp := TaskPosition{
TaskID: t.ID,
ProjectViewID: view.ID,
Position: calculateDefaultPosition(t.Index, t.Position),
tp, err := calculateNewPositionForTask(s, a, t, view)
if err != nil {
return err
}
err = tp.Update(s, a)
if err != nil {
return err
+9 -28
View File
@@ -27,9 +27,9 @@ import (
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/user"
"github.com/typesense/typesense-go/v2/typesense"
"github.com/typesense/typesense-go/v2/typesense/api"
"github.com/typesense/typesense-go/v2/typesense/api/pointer"
"github.com/typesense/typesense-go/typesense"
"github.com/typesense/typesense-go/typesense/api"
"github.com/typesense/typesense-go/typesense/api/pointer"
"xorm.io/xorm"
)
@@ -184,10 +184,6 @@ func CreateTypesenseCollections() error {
Name: "positions",
Type: "object",
},
{
Name: "positions.view_.*",
Type: "float",
},
{
Name: "buckets",
Type: "int64[]",
@@ -203,6 +199,8 @@ func CreateTypesenseCollections() error {
}
func ReindexAllTasks() (err error) {
tasks := make(map[int64]*Task)
s := db.NewSession()
defer s.Close()
@@ -220,7 +218,6 @@ func ReindexAllTasks() (err error) {
return fmt.Errorf("could not update last sync: %s", err.Error())
}
tasks := make(map[int64]*Task)
err = s.Find(tasks)
if err != nil {
return fmt.Errorf("could not get all tasks: %s", err.Error())
@@ -254,9 +251,6 @@ func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int6
if projectsCache == nil {
p, err = GetProjectSimpleByID(s, task.ProjectID)
if err != nil {
if IsErrProjectDoesNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("could not fetch project %d: %s", task.ProjectID, err.Error())
}
} else {
@@ -265,9 +259,6 @@ func getTypesenseTaskForTask(s *xorm.Session, task *Task, projectsCache map[int6
if !has {
p, err = GetProjectSimpleByID(s, task.ProjectID)
if err != nil {
if IsErrProjectDoesNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("could not fetch project %d: %s", task.ProjectID, err.Error())
}
projectsCache[task.ProjectID] = p
@@ -314,32 +305,22 @@ func reindexTasksInTypesense(s *xorm.Session, tasks map[int64]*Task) (err error)
if err != nil {
return err
}
if ttask == nil {
log.Debugf("Converted typesense task %d is nil, not indexing", task.ID)
continue
}
typesenseTasks = append(typesenseTasks, ttask)
}
response, err := typesenseClient.Collection("tasks").
_, err = typesenseClient.Collection("tasks").
Documents().
Import(context.Background(), typesenseTasks, &api.ImportDocumentsParams{
Action: pointer.String("emplace"),
Action: pointer.String("upsert"),
BatchSize: pointer.Int(100),
})
if err != nil {
log.Errorf("Could not upsert tasks into Typesense: %s", err)
log.Errorf("Could not upsert task into Typesense", err)
return err
}
for _, r := range response {
if r.Success {
continue
}
log.Errorf("Errors during index: [error=%s, document=%s]", r.Error, r.Document)
}
log.Debugf("Indexed %d tasks into Typesense", len(typesenseTasks))
log.Debugf("Indexed %d tasks into Typesense", len(tasks))
return nil
}
+24 -1
View File
@@ -17,11 +17,14 @@
package v1
import (
"net/http"
"strings"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
auth2 "code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/web/handler"
"net/http"
"github.com/labstack/echo/v4"
)
@@ -114,6 +117,7 @@ func UploadTaskAttachment(c echo.Context) error {
// @Produce octet-stream
// @Param id path int true "Task ID"
// @Param attachmentID path int true "Attachment ID"
// @Param preview_size query string false "The size of the preview image. Can be sm = 100px, md = 200px, lg = 400px or xl = 800px. If provided, a preview image will be returned if the attachment is an image."
// @Security JWTKeyAuth
// @Success 200 {file} blob "The attachment file."
// @Failure 403 {object} models.Message "No access to this task."
@@ -152,6 +156,17 @@ func GetTaskAttachment(c echo.Context) error {
return handler.HandleHTTPError(err)
}
// If the preview query parameter is set and the preview was already generated and cached, return the cached preview image
previewSize := models.GetPreviewSizeFromString(c.QueryParam("preview_size"))
if previewSize != models.PreviewSizeUnknown && strings.HasPrefix(taskAttachment.File.Mime, "image") {
previewFileBytes := taskAttachment.GetPreviewFromCache(previewSize)
if previewFileBytes != nil {
log.Debugf("Cached attachment image preview found for task attachment %v", taskAttachment.ID)
return c.Blob(http.StatusOK, "image/png", previewFileBytes)
}
}
// Open and send the file to the client
err = taskAttachment.File.LoadFileByID()
if err != nil {
@@ -164,6 +179,14 @@ func GetTaskAttachment(c echo.Context) error {
return handler.HandleHTTPError(err)
}
// If a preview is requested and the preview was not cached, we create the preview and cache it
if previewSize != models.PreviewSizeUnknown {
previewFileBytes := taskAttachment.GenerateAndSavePreviewToCache(previewSize)
if previewFileBytes != nil {
return c.Blob(http.StatusOK, "image/png", previewFileBytes)
}
}
http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File)
return nil
}
+4 -26
View File
@@ -52,7 +52,7 @@ type VikunjaCaldavProjectStorage struct {
}
// GetResources returns either all projects, links to the principal, or only one project, depending on the request
func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, withChildren bool) ([]data.Resource, error) {
func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, _ bool) ([]data.Resource, error) {
// It looks like we need to have the same handler for returning both the calendar home set and the user principal
// Since the client seems to ignore the whatever is being returned in the first request and just makes a second one
@@ -92,24 +92,6 @@ func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, withChildren
}
r := data.NewResource(rpath, &rr)
r.Name = vcls.project.Title
// If the request is withChildren (Depth: 1), we need to return all tasks of the project
if withChildren {
resources := []data.Resource{r}
// Check if there are tasks to iterate over
if vcls.project.Tasks != nil {
for i := range vcls.project.Tasks {
taskResource := VikunjaProjectResourceAdapter{
project: vcls.project,
task: &vcls.project.Tasks[i].Task,
isCollection: false,
}
addTaskResource(&vcls.project.Tasks[i].Task, &taskResource, &resources)
}
}
return resources, nil
}
return []data.Resource{r}, nil
}
@@ -181,7 +163,9 @@ func (vcls *VikunjaCaldavProjectStorage) GetResourcesByList(rpaths []string) (re
rr := VikunjaProjectResourceAdapter{
task: t,
}
addTaskResource(t, &rr, &resources)
r := data.NewResource(getTaskURL(t), &rr)
r.Name = t.Title
resources = append(resources, r)
}
return
@@ -687,9 +671,3 @@ func (vcls *VikunjaCaldavProjectStorage) getProjectRessource(isCollection bool)
return
}
func addTaskResource(task *models.Task, rr *VikunjaProjectResourceAdapter, resources *[]data.Resource) {
taskResourceInstance := data.NewResource(getTaskURL(task), rr)
taskResourceInstance.Name = task.Title
*resources = append(*resources, taskResourceInstance)
}