Compare commits

..

94 Commits

Author SHA1 Message Date
renovate 8d9ae6bd73 fix(deps): update dependency @kyvg/vue3-notification to v3.1.4
continuous-integration/drone/pr Build is failing Details
2024-02-04 11:20:05 +00:00
renovate d0dc86fd58 fix(deps): update dependency vue-i18n to v9.9.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-31 02:20:29 +00:00
renovate 0484923b8a fix(deps): update sentry-javascript monorepo to v7.99.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-30 17:18:57 +00:00
renovate 5f2fb01e90 fix(deps): update dependency floating-vue to v5.2.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-30 14:19:37 +00:00
renovate bd18524f36 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-30 00:19:43 +00:00
renovate 7375a87f2f fix(deps): update dependency @fortawesome/vue-fontawesome to v3.0.6
continuous-integration/drone/push Build is passing Details
2024-01-29 21:18:09 +00:00
renovate ccff276397 chore(deps): update pnpm to v8.15.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-29 20:19:25 +00:00
renovate 30b21fc11c fix(deps): update dependency floating-vue to v5.2.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-29 14:22:11 +00:00
renovate 7c98ddc20b fix(deps): update tiptap to v2.2.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-29 13:21:38 +00:00
renovate 6ba02a0f10 chore(deps): update pnpm to v8.15.0
continuous-integration/drone/push Build is passing Details
2024-01-29 07:32:55 +00:00
renovate 676d2b6215 chore(deps): update dependency @types/node to v20.11.10
continuous-integration/drone/push Build is passing Details
2024-01-29 07:32:26 +00:00
Frederick [Bot] 85e612451f chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-01-29 00:25:58 +00:00
kolaente d411de99f1
chore: release preparation
continuous-integration/drone/push Build is passing Details
2024-01-28 17:43:53 +01:00
kolaente 228d652b03
fix(kanban): make sure spacing between assignees and other task details works out evenly
continuous-integration/drone/push Build is passing Details
2024-01-28 16:41:24 +01:00
kolaente b3e2107503
fix(task): don't show assignee edit buttons and input when the user does not have the permission to edit
continuous-integration/drone/push Build is passing Details
2024-01-28 13:30:29 +01:00
kolaente a579a8e65f
fix(task): don't show edit button when the user does not have permission to edit the task 2024-01-28 13:24:58 +01:00
kolaente ee980e2a00
fix(openid): use the calculated redirect url when authenticating with openid providers
continuous-integration/drone/push Build is passing Details
Resolves https://github.com/go-vikunja/desktop/issues/12
2024-01-28 12:42:45 +01:00
renovate 394dbe0055 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-28 04:19:06 +00:00
renovate 30d599369f chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-27 00:18:54 +00:00
kolaente 631b02d2ee
chore: only show webhooks overview table when there are webhooks
continuous-integration/drone/push Build is passing Details
2024-01-27 00:01:11 +01:00
kolaente 326bfb557a
chore: only show webhooks overview table when there are webhooks
continuous-integration/drone/push Build is passing Details
2024-01-27 00:00:31 +01:00
kolaente cd0149ef69
fix(kanban): make sure the checklist summary uses the correct text color
continuous-integration/drone/push Build is passing Details
Related-To https://github.com/go-vikunja/frontend/issues/135
2024-01-26 21:44:20 +01:00
kolaente 78d4a518a3
fix(tasks): don't load tasks multiple times when viewing list or gantt view
continuous-integration/drone/push Build is passing Details
2024-01-26 21:33:20 +01:00
kolaente 3c1041902e
fix(table view): make sure popup does not overlap
continuous-integration/drone/push Build is passing Details
2024-01-26 21:22:51 +01:00
kolaente e3cae0ed7f
fix(filter): validate filter title field after loading a filter for edit
continuous-integration/drone/push Build is passing Details
Related to #3866
2024-01-26 11:29:46 +01:00
renovate fc8bd6a9ca chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-26 05:19:03 +00:00
renovate 5a6e5619e3 fix(deps): update dependency axios to v1.6.7
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-25 20:20:22 +00:00
renovate 9c9f806e62 fix(deps): update sentry-javascript monorepo to v7.98.0
continuous-integration/drone/push Build is passing Details
2024-01-25 13:55:42 +00:00
kolaente 67216579bc
fix(auth): correctly construct redirect url from current window href
continuous-integration/drone/push Build is passing Details
2024-01-25 14:24:30 +01:00
renovate a8df935ddb fix(deps): update sentry-javascript monorepo to v7.97.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-25 11:20:20 +00:00
renovate bb4746f226 chore(deps): update dev-dependencies
continuous-integration/drone/push Build is passing Details
2024-01-25 07:50:16 +00:00
renovate 31590236aa fix(deps): update dependency axios to v1.6.6
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-24 23:19:33 +00:00
renovate 00d48a6178 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-24 06:19:41 +00:00
renovate 5169cca8d8 fix(deps): update sentry-javascript monorepo to v7.95.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-23 19:18:41 +00:00
renovate 255a7d565c chore(deps): update pnpm to v8.14.3
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-23 10:20:44 +00:00
renovate 8dbaee5dfb chore(deps): update dev-dependencies to v6.19.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-23 00:19:30 +00:00
renovate 69b0b19482 fix(deps): update dependency date-fns to v3.3.1
continuous-integration/drone/push Build is passing Details
2024-01-22 10:44:49 +00:00
renovate eae89d37f1 chore(deps): update pnpm to v8.14.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-22 10:19:22 +00:00
renovate 7d19859816 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-22 00:18:43 +00:00
kolaente c7b70844c6
fix(color picker): when picking a color, the color picker should not be black afterwards
continuous-integration/drone/push Build is passing Details
2024-01-21 20:25:19 +01:00
kolaente b8c21c2ade
fix(labels): text and background combination in dark mode
continuous-integration/drone/push Build is passing Details
2024-01-21 20:20:00 +01:00
kolaente 57c99a22a0
fix(editor): use manual input prompt instead of window.prompt
continuous-integration/drone/push Build is passing Details
Resolves vikunja/desktop#184
2024-01-21 20:08:10 +01:00
kolaente 8ea97f3ffc
fix(editor): use a stable image id to prevent constant re-rendering
continuous-integration/drone/push Build is passing Details
2024-01-21 15:45:18 +01:00
kolaente 0b3604d167
fix(editor): render images without crashing
continuous-integration/drone/push Build is passing Details
2024-01-21 15:00:26 +01:00
kolaente c5ba7fcb73
fix(editor): focus the editor when clicking on the whole edit container
continuous-integration/drone/push Build is passing Details
2024-01-21 13:52:13 +01:00
kolaente 5a25685d53
fix(editor): don't bubble up changes when no changes were made
continuous-integration/drone/push Build is passing Details
Related https://community.vikunja.io/t/saving-an-empty-description-in-kanban-view-break-ui/1914/3
2024-01-21 13:44:40 +01:00
kolaente da311fce9e
fix(kanban): ensure text and icon color only depends on the card background, not on the color scheme
continuous-integration/drone/push Build is passing Details
Related https://github.com/go-vikunja/frontend/issues/135#issuecomment-1900701258
2024-01-21 00:10:05 +01:00
kolaente 0fdf1ca027
fix(notifications): read indicator size
continuous-integration/drone/push Build is passing Details
2024-01-21 00:01:04 +01:00
kolaente f8e907a8c1
fix(notifications): always left-align notification text
continuous-integration/drone/push Build is passing Details
2024-01-20 23:59:57 +01:00
kolaente af7ca8ad8f
fix(project): always use the appropriate color for task estimate during deletion dialoge
continuous-integration/drone/push Build is passing Details
2024-01-20 23:54:03 +01:00
nor 92f7d9ded5 feat: datepicker locale support (#3878)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3878
Reviewed-by: konrad <k@knt.li>
Co-authored-by: nor <zorodey@outlook.com>
Co-committed-by: nor <zorodey@outlook.com>
2024-01-20 18:50:00 +00:00
renovate 41ccaea78b fix(deps): update dependency date-fns to v3.3.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-20 06:19:03 +00:00
renovate c5696f3e2a chore(deps): update dependency vite to v5.0.12
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-20 00:19:28 +00:00
renovate 898707664c fix(deps): update sentry-javascript monorepo to v7.94.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-19 13:19:38 +00:00
renovate d0b5bef68a chore(deps): update dependency happy-dom to v13.2.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build was killed Details
2024-01-19 00:20:24 +00:00
renovate e395d4efdb fix(deps): update dependency vue to v3.4.15
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-18 14:19:39 +00:00
renovate ce54132868 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-18 07:18:54 +00:00
renovate 07d4d1e537 fix(deps): update dependency floating-vue to v5.2.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 13:19:31 +00:00
renovate a701b0452e fix(deps): update dependency floating-vue to v5.1.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 12:18:43 +00:00
renovate af65efcd27 chore(deps): update dev-dependencies (major) (#3890)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3890
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-17 09:17:35 +00:00
renovate dc2afb9e8d chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 07:18:31 +00:00
WofWca e123d4f825 chore(perf): import some modules dynamically (#3179)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3179
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2024-01-16 14:24:24 +00:00
renovate b72c963256 fix(deps): update dependency vue to v3.4.14
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-16 12:10:13 +00:00
renovate 149bbf17eb fix(deps): update dependency floating-vue to v5.1.0
continuous-integration/drone/push Build is passing Details
2024-01-16 12:01:54 +00:00
renovate 265d60cf42 fix(deps): update vueuse to v10.7.2
continuous-integration/drone/push Build is failing Details
2024-01-16 12:01:44 +00:00
renovate 23c9f51e73 fix(deps): update dependency sortablejs to v1.15.2
continuous-integration/drone/push Build is passing Details
2024-01-16 12:01:11 +00:00
renovate ff697d0c7a chore(deps): update dev-dependencies
continuous-integration/drone/push Build is passing Details
2024-01-16 11:50:52 +00:00
renovate 00588cf59f chore(deps): pin node.js (#3895)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #3895
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-16 11:49:00 +00:00
renovate 01089f4f3d fix(deps): update tiptap to v2.1.16 (#3892)
continuous-integration/drone/push Build was killed Details
Reviewed-on: #3892
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-16 11:17:28 +00:00
kolaente a7461d1ddd
chore(deps): increase renovate timeout
continuous-integration/drone/push Build is passing Details
2024-01-16 12:15:04 +01:00
kolaente a451189bb6
fix(test): make date assertion not brittle
continuous-integration/drone/push Build is passing Details
2024-01-16 10:36:29 +01:00
kolaente bf9af27fc3
fix(task): update due date when marking a task done
continuous-integration/drone/push Build is failing Details
2024-01-15 23:33:02 +01:00
kolaente 5619fda0f2
fix(task): bubble date changes from the picker up
continuous-integration/drone/push Build is failing Details
Resolves https://github.com/go-vikunja/frontend/issues/142
2024-01-15 23:23:57 +01:00
kolaente 167953b26b
fix(editor): use higher-contrast colors for links and code
continuous-integration/drone/push Build is passing Details
2024-01-15 22:11:24 +01:00
kolaente 664bf0a5f4
fix(tasks): make sure tasks show up if their parent task is not available in the current view
continuous-integration/drone/push Build is passing Details
Related https://github.com/go-vikunja/frontend/issues/136
Related https://community.vikunja.io/t/subtasks-are-hidden-when-parent-tasks-are-moved/1911
2024-01-15 21:46:47 +01:00
kolaente 5e991f3024
fix: lint
continuous-integration/drone/push Build is passing Details
2024-01-15 16:21:00 +01:00
kolaente 28050d9cd5
fix(labels): make color reset work
continuous-integration/drone/push Build is failing Details
2024-01-15 14:00:08 +01:00
kolaente e94b71d577
fix(editor): list icons
continuous-integration/drone/push Build is passing Details
2024-01-15 13:39:17 +01:00
renovate 336ce217d3 chore(deps): update node.js to v20.11 (#3888)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3888
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-11 11:30:14 +00:00
Frederick [Bot] ce01085951 chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-01-11 00:11:57 +00:00
kolaente 96a6d43a3f
fix(quick add magic): ensure month is removed from task text
continuous-integration/drone/push Build is passing Details
Resolves #3874
2024-01-10 23:54:42 +01:00
kolaente 13d63e34aa
fix(task): don't immediately re-trigger date change when nothing changed
continuous-integration/drone/push Build is passing Details
Resolves https://community.vikunja.io/t/reminder-duplication/76/21?u=kolaente
2024-01-10 23:27:14 +01:00
renovate a8441c72b8 fix(deps): update dependency vue to v3.4.8 (#3886)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3886
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 20:16:50 +00:00
renovate 230fa6ce66 fix(deps): update dependency floating-vue to v5 (#3887)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3887
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 18:18:53 +00:00
renovate 069c491fbd fix(deps): update sentry-javascript monorepo to v7.93.0 (#3859)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3859
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 16:01:02 +00:00
renovate a9eae95d67 chore(deps): update pnpm to v8.14.1 (#3885)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3885
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 15:57:10 +00:00
renovate 50502d9d11 fix(deps): update vueuse to v10.7.1 (#3872)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3872
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 15:04:27 +00:00
renovate 18af6edc82 fix(deps): update tiptap to v2.1.15 (#3884)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3884
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:49:03 +00:00
renovate d048b61eb3 fix(deps): update dependency floating-vue to v2.0.0 (#3883)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3883
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:36:14 +00:00
renovate 996607e670 fix(deps): update dependency dompurify to v3.0.8 (#3881)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3881
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:35:50 +00:00
renovate e33ebe1831 fix(deps): update dependency vue-i18n to v9.9.0 (#3880)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3880
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:27:02 +00:00
renovate 557b0ffec7 chore(deps): update dependency node to v20.11.0
continuous-integration/drone/push Build is passing Details
2024-01-10 12:04:17 +00:00
renovate dae6cdb9d7 fix(deps): update dependency @kyvg/vue3-notification to v3.1.3 (#3864)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3864
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:59:31 +00:00
renovate 158e4d690f chore(deps): update dev-dependencies (#3861)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #3861
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:51:20 +00:00
49 changed files with 2606 additions and 2195 deletions

View File

@ -42,7 +42,7 @@ steps:
# - .cache
- name: dependencies
image: node:20.10-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -55,7 +55,7 @@ steps:
# - restore-cache
- name: lint
image: node:20.10-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -66,7 +66,7 @@ steps:
- dependencies
- name: build-prod
image: node:20.10-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -77,7 +77,7 @@ steps:
- dependencies
- name: test-unit
image: node:20.10-alpine
image: node:20.11.0-alpine
pull: always
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
@ -87,7 +87,7 @@ steps:
- name: typecheck
failure: ignore
image: node:20.10-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -202,7 +202,7 @@ steps:
# - .cache
- name: build
image: node:20.10-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -285,7 +285,7 @@ steps:
# - .cache
- name: build
image: node:20.10-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -532,6 +532,6 @@ steps:
src/i18n/lang/en.json: en.json
---
kind: signature
hmac: ecb706a867b39f2501cc6cf587a535fe4cd6cfd0c339833a733d61a3349c5a54
hmac: a044c7c4db3c2a11299d4d118397e9d25be36db241723a1bbd0a2f9cc90ffdac
...

2
.nvmrc
View File

@ -1 +1 @@
20.10.0
20.11.0

View File

@ -9,6 +9,116 @@ All releases can be found on https://code.vikunja.io/frontend/releases.
The releases aim at the api versions which is why there are missing versions.
## [0.22.1] - 2024-01-28
### Bug Fixes
* *(auth)* Correctly construct redirect url from current window href
* *(ci)* Use working crowdin image
* *(ci)* Use working image for crowdin update step
* *(ci)* Use working crowdin image
* *(color picker)* When picking a color, the color picker should not be black afterwards
* *(editor)* List icons
* *(editor)* Use higher-contrast colors for links and code
* *(editor)* Don't bubble up changes when no changes were made
* *(editor)* Focus the editor when clicking on the whole edit container
* *(editor)* Render images without crashing
* *(editor)* Use a stable image id to prevent constant re-rendering
* *(editor)* Use manual input prompt instead of window.prompt
* *(filter)* Validate filter title field after loading a filter for edit
* *(kanban)* Ensure text and icon color only depends on the card background, not on the color scheme
* *(kanban)* Make sure the checklist summary uses the correct text color
* *(kanban)* Make sure spacing between assignees and other task details works out evenly
* *(labels)* Make color reset work
* *(labels)* Text and background combination in dark mode
* *(notifications)* Unread indicator spacing
* *(notifications)* Always left-align notification text
* *(notifications)* Read indicator size
* *(openid)* Use the full path when building the redirect url, not only the host
* *(openid)* Use the calculated redirect url when authenticating with openid providers
* *(project)* Always use the appropriate color for task estimate during deletion dialoge
* *(quick add magic)* Ensure month is removed from task text
* *(table view)* Make sure popup does not overlap
* *(task)* Don't immediately re-trigger date change when nothing changed
* *(task)* Bubble date changes from the picker up
* *(task)* Update due date when marking a task done
* *(task)* Don't show edit button when the user does not have permission to edit the task
* *(task)* Don't show assignee edit buttons and input when the user does not have the permission to edit
* *(tasks)* Make sure tasks show up if their parent task is not available in the current view
* *(tasks)* Don't load tasks multiple times when viewing list or gantt view
* *(test)* Make date assertion not brittle
* Lint ([5e991f3](5e991f3024f7856420614171ec66468eb2e2df63))
### Dependencies
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v2 (#3862)
* *(deps)* Update pnpm to v8.14.0
* *(deps)* Update dependency vue to v3.4.7 (#3873)
* *(deps)* Update dependency axios to v1.6.5 (#3871)
* *(deps)* Update dependency date-fns to v3 (#3857)
* *(deps)* Update dev-dependencies (#3861)
* *(deps)* Update dependency @kyvg/vue3-notification to v3.1.3 (#3864)
* *(deps)* Update dependency node to v20.11.0
* *(deps)* Update dependency vue-i18n to v9.9.0 (#3880)
* *(deps)* Update dependency dompurify to v3.0.8 (#3881)
* *(deps)* Update dependency floating-vue to v2.0.0 (#3883)
* *(deps)* Update tiptap to v2.1.15 (#3884)
* *(deps)* Update vueuse to v10.7.1 (#3872)
* *(deps)* Update pnpm to v8.14.1 (#3885)
* *(deps)* Update sentry-javascript monorepo to v7.93.0 (#3859)
* *(deps)* Update dependency floating-vue to v5 (#3887)
* *(deps)* Update dependency vue to v3.4.8 (#3886)
* *(deps)* Update node.js to v20.11 (#3888)
* *(deps)* Increase renovate timeout
* *(deps)* Update tiptap to v2.1.16 (#3892)
* *(deps)* Pin node.js (#3895)
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency sortablejs to v1.15.2
* *(deps)* Update vueuse to v10.7.2
* *(deps)* Update dependency floating-vue to v5.1.0
* *(deps)* Update dependency vue to v3.4.14
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies (major) (#3890)
* *(deps)* Update dependency floating-vue to v5.1.1
* *(deps)* Update dependency floating-vue to v5.2.0
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency vue to v3.4.15
* *(deps)* Update dependency happy-dom to v13.2.0
* *(deps)* Update sentry-javascript monorepo to v7.94.1
* *(deps)* Update dependency vite to v5.0.12
* *(deps)* Update dependency date-fns to v3.3.0
* *(deps)* Update dev-dependencies
* *(deps)* Update pnpm to v8.14.2
* *(deps)* Update dependency date-fns to v3.3.1
* *(deps)* Update dev-dependencies to v6.19.1
* *(deps)* Update pnpm to v8.14.3
* *(deps)* Update sentry-javascript monorepo to v7.95.0
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency axios to v1.6.6
* *(deps)* Update dev-dependencies
* *(deps)* Update sentry-javascript monorepo to v7.97.0
* *(deps)* Update sentry-javascript monorepo to v7.98.0
* *(deps)* Update dependency axios to v1.6.7
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
### Features
* *(reminders)* Show reminders in notifications bar
* Datepicker locale support (#3878) ([92f7d9d](92f7d9ded5d56b95ba7d647eba01372f6ef682ad))
### Miscellaneous Tasks
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(perf)* Import some modules dynamically (#3179)
* Only show webhooks overview table when there are webhooks ([326bfb5](326bfb557ab359fa154b163f5dd957928f46d3ec))
* Only show webhooks overview table when there are webhooks ([631b02d](631b02d2eedc4a403b7c55f1c56ceaeca5379bf5))
## [0.22.0] - 2023-12-19
### Bug Fixes

View File

@ -3,7 +3,7 @@
# │─││ │││ │ │
# ┘─┘┘─┘┘┘─┘┘─┘
FROM --platform=$BUILDPLATFORM node:20.10-alpine AS builder
FROM --platform=$BUILDPLATFORM node:20.11.0-alpine AS builder
WORKDIR /build

View File

@ -4,7 +4,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.22.0-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.22.1-brightgreen.svg)](https://dl.vikunja.io)
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
This is the web frontend for Vikunja, written in Vue.js.

View File

@ -541,6 +541,86 @@ describe('Task', () => {
.should('contain', 'Success')
})
it('Can set a due date to a specific date for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Due Date')
.click()
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker .show')
.click()
cy.get('.datepicker-popup .flatpickr-innerContainer .flatpickr-days .flatpickr-day.today')
.click()
cy.get('[data-cy="closeDatepicker"]')
.contains('Confirm')
.click()
const today = new Date()
const day = today.toLocaleString('default', {day: '2-digit'})
const month = today.toLocaleString('default', {month: 'short'})
const year = today.toLocaleString('default', {year: 'numeric'})
const date = `${day} ${month} ${year}, 12:00:00`
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker-popup')
.should('not.exist')
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input')
.should('contain.text', date)
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can change a due date to a specific date for a task', () => {
const dueDate = new Date()
dueDate.setHours(12)
dueDate.setMinutes(0)
dueDate.setSeconds(0)
dueDate.setDate(1)
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
due_date: dueDate.toISOString(),
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Due Date')
.click()
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker .show')
.click()
cy.get('.datepicker-popup .flatpickr-innerContainer .flatpickr-days .flatpickr-day.today')
.click()
cy.get('[data-cy="closeDatepicker"]')
.contains('Confirm')
.click()
const today = new Date()
const day = today.toLocaleString('default', {day: '2-digit'})
const month = today.toLocaleString('default', {month: 'short'})
const year = today.toLocaleString('default', {year: 'numeric'})
const date = `${day} ${month} ${year}, 12:00:00`
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker-popup')
.should('not.exist')
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input')
.should('contain.text', date)
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can set a reminder', () => {
TaskReminderFactory.truncate()
const tasks = TaskFactory.create(1, {
@ -645,7 +725,7 @@ describe('Task', () => {
.click()
cy.get('.reminder-options-popup .card-content .reminder-period input')
.first()
.type('10')
.type('{selectall}10')
cy.get('.reminder-options-popup .card-content .reminder-period select')
.first()
.select('days')
@ -771,7 +851,7 @@ describe('Task', () => {
.should('exist')
})
it.only('Can check items off a checklist', () => {
it('Can check items off a checklist', () => {
const tasks = TaskFactory.create(1, {
id: 1,
description: `
@ -858,7 +938,7 @@ describe('Task', () => {
method: 'PUT',
url: `${Cypress.env('API_URL')}/tasks/${tasks[0].id}/attachments`,
headers: {
'Authorization': `Bearer ${window.localStorage.getItem('token')}`,
'Authorization': `Bearer ${window.localStorage.getItem('token')}`,
'Content-Type': 'multipart/form-data',
},
body: formData,

View File

@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@8.14.0",
"packageManager": "pnpm@8.15.1",
"keywords": [
"todo",
"productivity",
@ -48,61 +48,61 @@
"@fortawesome/fontawesome-svg-core": "6.5.1",
"@fortawesome/free-regular-svg-icons": "6.5.1",
"@fortawesome/free-solid-svg-icons": "6.5.1",
"@fortawesome/vue-fontawesome": "3.0.5",
"@fortawesome/vue-fontawesome": "3.0.6",
"@github/hotkey": "3.1.0",
"@infectoone/vue-ganttastic": "2.2.0",
"@intlify/unplugin-vue-i18n": "2.0.0",
"@kyvg/vue3-notification": "3.1.3",
"@sentry/tracing": "7.88.0",
"@sentry/vue": "7.88.0",
"@tiptap/core": "2.1.13",
"@tiptap/extension-blockquote": "2.1.13",
"@tiptap/extension-bold": "2.1.13",
"@tiptap/extension-bullet-list": "2.1.13",
"@tiptap/extension-code": "2.1.13",
"@tiptap/extension-code-block-lowlight": "2.1.13",
"@tiptap/extension-document": "2.1.13",
"@tiptap/extension-dropcursor": "2.1.13",
"@tiptap/extension-gapcursor": "2.1.13",
"@tiptap/extension-hard-break": "2.1.13",
"@tiptap/extension-heading": "2.1.13",
"@tiptap/extension-history": "2.1.13",
"@tiptap/extension-horizontal-rule": "2.1.13",
"@tiptap/extension-image": "2.1.13",
"@tiptap/extension-italic": "2.1.13",
"@tiptap/extension-link": "2.1.13",
"@tiptap/extension-list-item": "2.1.13",
"@tiptap/extension-ordered-list": "2.1.13",
"@tiptap/extension-paragraph": "2.1.13",
"@tiptap/extension-placeholder": "2.1.13",
"@tiptap/extension-strike": "2.1.13",
"@tiptap/extension-table": "2.1.13",
"@tiptap/extension-table-cell": "2.1.13",
"@tiptap/extension-table-header": "2.1.13",
"@tiptap/extension-table-row": "2.1.13",
"@tiptap/extension-task-item": "2.1.13",
"@tiptap/extension-task-list": "2.1.13",
"@tiptap/extension-text": "2.1.13",
"@tiptap/extension-typography": "2.1.13",
"@tiptap/extension-underline": "2.1.13",
"@tiptap/pm": "2.1.13",
"@tiptap/suggestion": "2.1.13",
"@tiptap/vue-3": "2.1.13",
"@kyvg/vue3-notification": "3.1.4",
"@sentry/tracing": "7.99.0",
"@sentry/vue": "7.99.0",
"@tiptap/core": "2.2.0",
"@tiptap/extension-blockquote": "2.2.0",
"@tiptap/extension-bold": "2.2.0",
"@tiptap/extension-bullet-list": "2.2.0",
"@tiptap/extension-code": "2.2.0",
"@tiptap/extension-code-block-lowlight": "2.2.0",
"@tiptap/extension-document": "2.2.0",
"@tiptap/extension-dropcursor": "2.2.0",
"@tiptap/extension-gapcursor": "2.2.0",
"@tiptap/extension-hard-break": "2.2.0",
"@tiptap/extension-heading": "2.2.0",
"@tiptap/extension-history": "2.2.0",
"@tiptap/extension-horizontal-rule": "2.2.0",
"@tiptap/extension-image": "2.2.0",
"@tiptap/extension-italic": "2.2.0",
"@tiptap/extension-link": "2.2.0",
"@tiptap/extension-list-item": "2.2.0",
"@tiptap/extension-ordered-list": "2.2.0",
"@tiptap/extension-paragraph": "2.2.0",
"@tiptap/extension-placeholder": "2.2.0",
"@tiptap/extension-strike": "2.2.0",
"@tiptap/extension-table": "2.2.0",
"@tiptap/extension-table-cell": "2.2.0",
"@tiptap/extension-table-header": "2.2.0",
"@tiptap/extension-table-row": "2.2.0",
"@tiptap/extension-task-item": "2.2.0",
"@tiptap/extension-task-list": "2.2.0",
"@tiptap/extension-text": "2.2.0",
"@tiptap/extension-typography": "2.2.0",
"@tiptap/extension-underline": "2.2.0",
"@tiptap/pm": "2.2.0",
"@tiptap/suggestion": "2.2.0",
"@tiptap/vue-3": "2.2.0",
"@types/is-touch-device": "1.0.2",
"@types/lodash.clonedeep": "4.5.9",
"@vueuse/core": "10.7.0",
"@vueuse/router": "10.7.0",
"axios": "1.6.5",
"@vueuse/core": "10.7.2",
"@vueuse/router": "10.7.2",
"axios": "1.6.7",
"blurhash": "2.0.5",
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
"date-fns": "3.2.0",
"date-fns": "3.3.1",
"dayjs": "1.11.10",
"dompurify": "3.0.6",
"dompurify": "3.0.8",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.31",
"floating-vue": "2.0.0-beta.24",
"floating-vue": "5.2.2",
"is-touch-device": "1.0.1",
"klona": "2.0.6",
"lodash.debounce": "4.0.8",
@ -110,25 +110,25 @@
"pinia": "2.1.7",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"sortablejs": "1.15.1",
"sortablejs": "1.15.2",
"tippy.js": "6.3.7",
"ufo": "1.3.2",
"vue": "3.4.7",
"vue": "3.4.15",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.3",
"vue-i18n": "9.8.0",
"vue-i18n": "9.9.1",
"vue-router": "4.2.5",
"workbox-precaching": "7.0.0",
"zhyswan-vuedraggable": "4.1.3"
},
"devDependencies": {
"@4tw/cypress-drag-drop": "2.2.5",
"@cypress/vite-dev-server": "5.0.6",
"@cypress/vite-dev-server": "5.0.7",
"@cypress/vue": "6.0.0",
"@faker-js/faker": "8.3.1",
"@histoire/plugin-screenshot": "0.17.6",
"@histoire/plugin-vue": "0.17.6",
"@rushstack/eslint-patch": "1.6.1",
"@faker-js/faker": "8.4.0",
"@histoire/plugin-screenshot": "0.17.8",
"@histoire/plugin-vue": "0.17.9",
"@rushstack/eslint-patch": "1.7.2",
"@tsconfig/node18": "18.2.2",
"@types/codemirror": "5.60.15",
"@types/dompurify": "3.0.5",
@ -136,44 +136,44 @@
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
"@types/node": "20.10.5",
"@types/node": "20.11.10",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.7",
"@typescript-eslint/eslint-plugin": "6.15.0",
"@typescript-eslint/parser": "6.15.0",
"@vitejs/plugin-legacy": "5.2.0",
"@vitejs/plugin-vue": "4.5.2",
"@typescript-eslint/eslint-plugin": "6.20.0",
"@typescript-eslint/parser": "6.20.0",
"@vitejs/plugin-legacy": "5.3.0",
"@vitejs/plugin-vue": "5.0.3",
"@vue/eslint-config-typescript": "12.0.0",
"@vue/test-utils": "2.4.3",
"@vue/test-utils": "2.4.4",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.16",
"browserslist": "4.22.2",
"caniuse-lite": "1.0.30001570",
"autoprefixer": "10.4.17",
"browserslist": "4.22.3",
"caniuse-lite": "1.0.30001581",
"css-has-pseudo": "6.0.1",
"csstype": "3.1.3",
"cypress": "13.6.1",
"esbuild": "0.19.10",
"cypress": "13.6.3",
"esbuild": "0.20.0",
"eslint": "8.56.0",
"eslint-plugin-vue": "9.19.2",
"happy-dom": "12.10.3",
"histoire": "0.17.6",
"postcss": "8.4.32",
"eslint-plugin-vue": "9.20.1",
"happy-dom": "13.3.5",
"histoire": "0.17.9",
"postcss": "8.4.33",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.1",
"postcss-preset-env": "9.3.0",
"rollup": "4.9.1",
"rollup-plugin-visualizer": "5.11.0",
"sass": "1.69.5",
"rollup": "4.9.6",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.70.0",
"start-server-and-test": "2.0.3",
"typescript": "5.3.3",
"vite": "5.0.10",
"vite": "5.0.12",
"vite-plugin-inject-preload": "1.3.3",
"vite-plugin-pwa": "0.17.4",
"vite-plugin-pwa": "0.17.5",
"vite-plugin-sentry": "1.3.0",
"vite-svg-loader": "5.1.0",
"vitest": "1.0.4",
"vue-tsc": "1.8.25",
"vitest": "1.2.2",
"vue-tsc": "1.8.27",
"wait-on": "7.2.0",
"workbox-cli": "7.0.0"
},

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,11 @@
"extends": [
"config:js-app"
],
"hostRules": [
{
"timeout": 600000
}
],
"packageRules": [
{
"matchPackageNames": ["happy-dom"],

View File

@ -37,8 +37,6 @@ import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import Ready from '@/components/misc/ready.vue'
import {setLanguage} from '@/i18n'
import AccountDeleteService from '@/services/accountDelete'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
import {useBaseStore} from '@/stores/base'
@ -48,6 +46,9 @@ import {useBodyClass} from '@/composables/useBodyClass'
import AddToHomeScreen from '@/components/home/AddToHomeScreen.vue'
import DemoMode from '@/components/home/DemoMode.vue'
const importAccountDeleteService = () => import('@/services/accountDelete')
const importMessage = () => import('@/message')
const baseStore = useBaseStore()
const authStore = useAuthStore()
const router = useRouter()
@ -68,8 +69,11 @@ watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
return
}
const messageP = importMessage()
const AccountDeleteService = (await importAccountDeleteService()).default
const accountDeletionService = new AccountDeleteService()
await accountDeletionService.confirm(accountDeletionConfirm)
const {success} = await messageP
success({message: t('user.deletion.confirmSuccess')})
authStore.refreshUserInfo()
}, { immediate: true })

View File

@ -81,9 +81,8 @@ import Popup from '@/components/misc/popup.vue'
import {DATE_RANGES} from '@/components/date/dateRanges'
import BaseButton from '@/components/base/BaseButton.vue'
import DatemathHelp from '@/components/date/datemathHelp.vue'
import {useAuthStore} from '@/stores/auth'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const authStore = useAuthStore()
const {t} = useI18n({useScope: 'global'})
const emit = defineEmits(['update:modelValue'])
@ -93,8 +92,6 @@ const props = defineProps({
},
})
// FIXME: This seems to always contain the default value - that breaks the picker
const weekStart = computed(() => authStore.settings.weekStart ?? 0)
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,
@ -102,9 +99,7 @@ const flatPickerConfig = computed(() => ({
enableTime: false,
wrap: true,
mode: 'range',
locale: {
firstDayOf7Days: weekStart.value,
},
locale: getFlatpickrLanguage(),
}))
const showHowItWorks = ref(false)

View File

@ -64,6 +64,15 @@ const emit = defineEmits(['update:modelValue'])
watch(
() => modelValue,
(newValue) => {
if (newValue === '' || newValue.startsWith('var(')) {
color.value = ''
return
}
if (!newValue.startsWith('#') && (newValue.length === 6 || newValue.length === 3)) {
newValue = `#${newValue}`
}
color.value = newValue
},
{immediate: true},

View File

@ -80,8 +80,8 @@ import {formatDate} from '@/helpers/time/formatDate'
import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {useAuthStore} from '@/stores/auth'
import {useI18n} from 'vue-i18n'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const props = defineProps({
modelValue: {
@ -105,8 +105,6 @@ watch(
{immediate: true},
)
const authStore = useAuthStore()
const weekStart = computed(() => authStore.settings.weekStart)
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,
@ -114,9 +112,7 @@ const flatPickerConfig = computed(() => ({
enableTime: true,
time_24hr: true,
inline: true,
locale: {
firstDayOfWeek: weekStart.value,
},
locale: getFlatpickrLanguage(),
}))
// Since flatpickr dates are strings, we need to convert them to native date objects.
@ -128,6 +124,12 @@ const flatPickrDate = computed({
return
}
if (date.value !== null) {
const oldDate = formatDate(date.value, 'yyy-LL-dd H:mm')
if (oldDate === newValue) {
return
}
}
date.value = createDateFromString(newValue)
updateData()
},
@ -155,10 +157,6 @@ function updateData() {
}
function setDate(dateString: string) {
if (date.value === null) {
date.value = new Date()
}
const interval = calculateDayInterval(dateString)
const newDate = new Date()
newDate.setDate(newDate.getDate() + interval)
@ -166,7 +164,6 @@ function setDate(dateString: string) {
newDate.setMinutes(0)
newDate.setSeconds(0)
date.value = newDate
flatPickrDate.value = newDate
updateData()
}

View File

@ -110,7 +110,7 @@
v-tooltip="$t('input.editor.bulletList')"
>
<span class="icon">
<icon :icon="['fa', 'fa-list-ol']"/>
<icon :icon="['fa', 'fa-list-ul']"/>
</span>
</BaseButton>
<BaseButton
@ -120,7 +120,7 @@
v-tooltip="$t('input.editor.orderedList')"
>
<span class="icon">
<icon :icon="['fa', 'fa-list-ul']"/>
<icon :icon="['fa', 'fa-list-ol']"/>
</span>
</BaseButton>
<BaseButton
@ -336,6 +336,7 @@ import {ref} from 'vue'
import {Editor} from '@tiptap/vue-3'
import BaseButton from '@/components/base/BaseButton.vue'
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
const {
editor = null,
@ -353,29 +354,8 @@ function openImagePicker() {
document.getElementById('tiptap__image-upload').click()
}
function setLink() {
const previousUrl = editor.getAttributes('link').href
const url = window.prompt('URL', previousUrl)
// cancelled
if (url === null) {
return
}
// empty
if (url === '') {
editor.chain().focus().extendMarkRange('link').unsetLink().run()
return
}
// update link
editor
.chain()
.focus()
.extendMarkRange('link')
.setLink({href: url, target: '_blank'})
.run()
function setLink(event) {
setLinkInEditor(event.target.getBoundingClientRect(), editor)
}
</script>

View File

@ -64,6 +64,7 @@
class="tiptap__editor"
:class="{'tiptap__editor-is-edit-enabled': isEditing}"
:editor="editor"
@click="focusIfEditing()"
/>
<input
@ -75,7 +76,7 @@
@change="addImage"
/>
<ul class="tiptap__editor-actions d-print-none" v-if="bottomActions.length === 0 && !isEditing">
<ul class="tiptap__editor-actions d-print-none" v-if="bottomActions.length === 0 && !isEditing && isEditEnabled">
<li>
<BaseButton
@click="setEdit"
@ -110,6 +111,7 @@
variant="secondary"
:shadow="false"
v-cy="'saveEditor'"
:disabled="!contentHasChanged"
>
{{ $t('misc.save') }}
</x-button>
@ -171,8 +173,9 @@ import XButton from '@/components/input/button.vue'
import {Placeholder} from '@tiptap/extension-placeholder'
import {eventToHotkeyString} from '@github/hotkey'
import {mergeAttributes} from '@tiptap/core'
import {createRandomID} from '@/helpers/randomId'
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
import inputPrompt from '@/helpers/inputPrompt'
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
@ -227,19 +230,20 @@ const CustomImage = Image.extend({
renderHTML({HTMLAttributes}) {
if (HTMLAttributes.src?.startsWith(window.API_URL) || HTMLAttributes['data-src']?.startsWith(window.API_URL)) {
const imageUrl = HTMLAttributes['data-src'] ?? HTMLAttributes.src
const id = 'tiptap-image-' + createRandomID()
// The url is something like /tasks/<id>/attachments/<id>
const parts = imageUrl.slice(window.API_URL.length + 1).split('/')
const taskId = Number(parts[1])
const attachmentId = Number(parts[3])
const cacheKey: CacheKey = `${taskId}-${attachmentId}`
const id = 'tiptap-image-' + cacheKey
nextTick(async () => {
const img = document.getElementById(id)
if (!img) return
// The url is something like /tasks/<id>/attachments/<id>
const parts = imageUrl.slice(window.API_URL.length + 1).split('/')
const taskId = Number(parts[1])
const attachmentId = Number(parts[3])
const cacheKey: CacheKey = `${taskId}-${attachmentId}`
if (typeof loadedAttachments.value[cacheKey] === 'undefined') {
const attachment = new AttachmentModel({taskId: taskId, id: attachmentId})
@ -286,8 +290,18 @@ const {
const emit = defineEmits(['update:modelValue', 'save'])
const internalMode = ref<Mode>('edit')
const internalMode = ref<Mode>('preview')
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled)
const contentHasChanged = ref<boolean>(false)
watch(
() => internalMode.value,
mode => {
if (mode === 'preview') {
contentHasChanged.value = false
}
},
)
const editor = useEditor({
content: modelValue,
@ -308,7 +322,9 @@ const editor = useEditor({
addKeyboardShortcuts() {
return {
'Mod-Enter': () => {
bubbleSave()
if (contentHasChanged.value) {
bubbleSave()
}
},
}
},
@ -420,6 +436,7 @@ function bubbleNow() {
return
}
contentHasChanged.value = true
emit('update:modelValue', editor.value?.getHTML())
}
@ -455,7 +472,7 @@ function uploadAndInsertFiles(files: File[] | FileList) {
})
}
function addImage() {
async function addImage(event) {
if (typeof uploadCallback !== 'undefined') {
const files = uploadInputRef.value?.files
@ -469,7 +486,7 @@ function addImage() {
return
}
const url = window.prompt('URL')
const url = await inputPrompt(event.target.getBoundingClientRect())
if (url) {
editor.value?.chain().focus().setImage({src: url}).run()
@ -477,34 +494,8 @@ function addImage() {
}
}
function setLink() {
const previousUrl = editor.value?.getAttributes('link').href
const url = window.prompt('URL', previousUrl)
// cancelled
if (url === null) {
return
}
// empty
if (url === '') {
editor.value
?.chain()
.focus()
.extendMarkRange('link')
.unsetLink()
.run()
return
}
// update link
editor.value
?.chain()
.focus()
.extendMarkRange('link')
.setLink({href: url, target: '_blank'})
.run()
function setLink(event) {
setLinkInEditor(event.target.getBoundingClientRect(), editor.value)
}
onMounted(async () => {
@ -558,6 +549,7 @@ function setFocusToEditor(event) {
event.target.contentEditable === 'true') {
return
}
event.preventDefault()
if (!isEditing.value && isEditEnabled) {
@ -567,6 +559,12 @@ function setFocusToEditor(event) {
editor.value?.commands.focus()
}
function focusIfEditing() {
if (isEditing.value) {
editor.value?.commands.focus()
}
}
function clickTasklistCheckbox(event) {
event.stopImmediatePropagation()
@ -671,36 +669,17 @@ watch(
line-height: 1.1;
}
a {
color: #68cef8;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
background-color: var(--grey-200);
color: var(--grey-700);
}
pre {
background: #0d0d0d;
color: #fff;
background: var(--grey-200);
color: var(--grey-700);
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
}
pre {
background: #0d0d0d;
color: #fff;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
border-radius: $radius;
code {
color: inherit;
@ -711,7 +690,7 @@ watch(
.hljs-comment,
.hljs-quote {
color: #616161;
color: var(--grey-500);
}
.hljs-variable,
@ -724,7 +703,7 @@ watch(
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #f98181;
color: var(--code-variable);
}
.hljs-number,
@ -734,23 +713,23 @@ watch(
.hljs-literal,
.hljs-type,
.hljs-params {
color: #fbbc88;
color: var(--code-literal);
}
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #b9f18d;
color: var(--code-symbol);
}
.hljs-title,
.hljs-section {
color: #faf594;
color: var(--code-section);
}
.hljs-keyword,
.hljs-selector-tag {
color: #70cff8;
color: var(--code-keyword);
}
.hljs-emphasis {

View File

@ -0,0 +1,26 @@
import inputPrompt from '@/helpers/inputPrompt'
export async function setLinkInEditor(pos, editor) {
const previousUrl = editor?.getAttributes('link').href || ''
const url = await inputPrompt(pos, previousUrl)
// empty
if (url === '') {
editor
?.chain()
.focus()
.extendMarkRange('link')
.unsetLink()
.run()
return
}
// update link
editor
?.chain()
.focus()
.extendMarkRange('link')
.setLink({href: url, target: '_blank'})
.run()
}

View File

@ -1,5 +1,5 @@
<template>
<slot name="trigger" :isOpen="open" :toggle="toggle"></slot>
<slot name="trigger" :isOpen="open" :toggle="toggle" :close="close"></slot>
<div
class="popup"
:class="{
@ -8,7 +8,7 @@
}"
ref="popup"
>
<slot name="content" :isOpen="open" :toggle="toggle"/>
<slot name="content" :isOpen="open" :toggle="toggle" :close="close"/>
</div>
</template>
@ -53,6 +53,7 @@ onClickOutside(popup, () => {
overflow: hidden;
position: absolute;
top: 1rem;
z-index: 100;
&.is-open {
opacity: 1;

View File

@ -27,7 +27,7 @@
<span class="has-text-weight-bold mr-1" v-if="n.notification.doer">
{{ getDisplayName(n.notification.doer) }}
</span>
<BaseButton @click="() => to(n, index)()">
<BaseButton @click="() => to(n, index)()" class="has-text-left">
{{ n.toText(userInfo) }}
</BaseButton>
</div>
@ -223,6 +223,7 @@ async function markAllRead() {
background: var(--primary);
border-radius: 100%;
margin: 0 .5rem;
flex-shrink: 0;
&.read {
background: transparent;

View File

@ -36,7 +36,7 @@ import Filters from '@/components/project/partials/filters.vue'
import {getDefaultParams} from '@/composables/useTaskList'
const props = defineProps({
const props = defineProps({
modelValue: {
required: true,
},
@ -48,6 +48,9 @@ const value = computed({
return props.modelValue
},
set(value) {
if(props.modelValue === value) {
return
}
emit('update:modelValue', value)
},
})
@ -59,7 +62,7 @@ watch(
},
{immediate: true},
)
const hasFilters = computed(() => {
// this.value also contains the page parameter which we don't want to include in filters
// eslint-disable-next-line no-unused-vars

View File

@ -61,6 +61,8 @@ import {
import Loading from '@/components/misc/loading.vue'
import {MILLISECONDS_A_DAY} from '@/constants/date'
import {useWeekDayFromDate} from '@/helpers/time/formatDate'
import dayjs from 'dayjs'
import {useDayjsLanguageSync} from '@/i18n/useDayjsLanguageSync'
export interface GanttChartProps {
isLoading: boolean,
@ -81,8 +83,8 @@ const emit = defineEmits<{
const {tasks, filters} = toRefs(props)
// setup dayjs for vue-ganttastic
const dayjsLanguageLoading = ref(false)
// const dayjsLanguageLoading = useDayjsLanguageSync(dayjs)
// const dayjsLanguageLoading = ref(false)
const dayjsLanguageLoading = useDayjsLanguageSync(dayjs)
extendDayjs()
const ganttContainer = ref(null)

View File

@ -44,7 +44,7 @@ import flatPickr from 'vue-flatpickr-component'
import TaskService from '@/services/task'
import type {ITask} from '@/modelTypes/ITask'
import {useAuthStore} from '@/stores/auth'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const {
modelValue,
@ -55,7 +55,6 @@ const {
const emit = defineEmits(['update:modelValue'])
const {t} = useI18n({useScope: 'global'})
const authStore = useAuthStore()
const taskService = shallowReactive(new TaskService())
const task = ref<ITask>()
@ -102,9 +101,7 @@ const flatPickerConfig = computed(() => ({
enableTime: true,
time_24hr: true,
inline: true,
locale: {
firstDayOfWeek: authStore.settings.weekStart,
},
locale: getFlatpickrLanguage(),
}))
function deferDays(days: number) {

View File

@ -12,7 +12,7 @@
:autocomplete-enabled="false"
>
<template #items="{items}">
<assignee-list :assignees="items" :remove="removeAssignee"/>
<assignee-list :assignees="items" :remove="removeAssignee" :disabled="disabled"/>
</template>
<template #searchResult="{option: user}">
<user :avatar-size="24" :show-username="true" :user="user"/>

View File

@ -51,6 +51,7 @@ import Multiselect from '@/components/input/multiselect.vue'
import type {ILabel} from '@/modelTypes/ILabel'
import {useLabelStore} from '@/stores/labels'
import {useTaskStore} from '@/stores/tasks'
import {getRandomColorHex} from '@/helpers/color/randomColor'
const props = defineProps({
modelValue: {
@ -132,7 +133,10 @@ async function createAndAddLabel(title: string) {
return
}
const newLabel = await labelStore.createLabel(new LabelModel({title}))
const newLabel = await labelStore.createLabel(new LabelModel({
title,
hexColor: getRandomColorHex(),
}))
addLabel(newLabel, false)
labels.value.push(newLabel)
success({message: t('task.label.addCreateSuccess')})

View File

@ -57,10 +57,9 @@
v-if="task.assignees.length > 0"
:assignees="task.assignees"
:avatar-size="24"
class="ml-1"
:inline="true"
class="mr-1"
/>
<checklist-summary :task="task"/>
<checklist-summary :task="task" class="checklist"/>
<span class="icon" v-if="task.attachments.length > 0">
<icon icon="paperclip"/>
</span>
@ -218,15 +217,20 @@ $task-background: var(--white);
display: flex;
flex-wrap: wrap;
align-items: center;
margin-top: .25rem;
:deep(.tag),
:deep(.checklist-summary),
.assignees,
.icon,
.priority-label {
margin-top: .25rem;
margin-right: .25rem;
}
:deep(.checklist-summary) {
padding-left: 0;
}
.assignees {
display: flex;
@ -292,25 +296,34 @@ $task-background: var(--white);
.priority-label {
background: hsl(220, 13%, 91%);
}
.footer :deep(.checklist-summary) {
color: hsl(216.9, 19.1%, 26.7%); // grey-700
}
}
&.has-light-text {
--white: hsla(var(--white-h), var(--white-s), var(--white-l), var(--white-a)) !important;
color: var(--white);
.task-id {
color: var(--grey-200);
color: hsl(220, 13%, 91%); // grey-200;
}
.footer .icon,
.due-date,
.priority-label {
background: var(--grey-800);
background: hsl(215, 27.9%, 16.9%); // grey-800
}
.footer {
.icon svg {
fill: var(--white);
}
:deep(.checklist-summary) {
color: hsl(220, 13%, 91%); // grey-200
}
}
}
}

View File

@ -9,7 +9,7 @@
{{ reminderText }}
</SimpleButton>
</template>
<template #content="{isOpen, toggle}">
<template #content="{isOpen, close}">
<Card class="reminder-options-popup" :class="{'is-open': isOpen}" :padding="false">
<div class="options" v-if="activeForm === null">
<SimpleButton
@ -17,7 +17,7 @@
:key="k"
class="option-button"
:class="{'currently-active': p.relativePeriod === modelValue?.relativePeriod && modelValue?.relativeTo === p.relativeTo}"
@click="setReminderFromPreset(p, toggle)"
@click="setReminderFromPreset(p, close)"
>
{{ formatReminder(p) }}
</SimpleButton>
@ -40,20 +40,20 @@
<ReminderPeriod
v-if="activeForm === 'relative'"
v-model="reminder"
@update:modelValue="updateDataAndMaybeClose(toggle)"
@update:modelValue="updateDataAndMaybeClose(close)"
/>
<DatepickerInline
v-if="activeForm === 'absolute'"
v-model="reminderDate"
@update:modelValue="setReminderDate(toggle)"
@update:modelValue="setReminderDate(close)"
/>
<x-button
v-if="showFormSwitch !== null"
class="reminder__close-button"
:shadow="false"
@click="toggle"
@click="updateDataAndMaybeClose(close)"
>
{{ $t('misc.confirm') }}
</x-button>
@ -148,25 +148,26 @@ function updateData() {
}
}
function setReminderDate(toggle) {
function setReminderDate(close) {
reminder.value.reminder = reminderDate.value === null
? null
: new Date(reminderDate.value)
reminder.value.relativeTo = null
reminder.value.relativePeriod = 0
updateDataAndMaybeClose(toggle)
updateDataAndMaybeClose(close)
}
function setReminderFromPreset(preset, toggle) {
function setReminderFromPreset(preset, close) {
reminder.value = preset
updateData()
toggle()
close()
}
function updateDataAndMaybeClose(toggle) {
function updateDataAndMaybeClose(close) {
updateData()
if (clearAfterUpdate) {
toggle()
close()
}
}

View File

@ -55,6 +55,7 @@ import TaskReminderModel from '@/models/taskReminder'
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
import {REMINDER_PERIOD_RELATIVE_TO_TYPES, type IReminderPeriodRelativeTo} from '@/types/IReminderPeriodRelativeTo'
import {useDebounceFn} from '@vueuse/core'
const {
modelValue,
@ -105,7 +106,7 @@ function updateData() {
reminder.value.relativeTo = period.value.relativeTo
reminder.value.reminder = null
emit('update:modelValue', reminder.value)
useDebounceFn(() => emit('update:modelValue', reminder.value), 1000)
}
</script>

View File

@ -288,6 +288,7 @@ async function markAsDone(checked: boolean) {
title: t('task.undo'),
callback: () => undoDone(checked),
}])
updateDueDate()
}
if (checked) {

View File

@ -4,8 +4,8 @@
* @param color
* @returns {string}
*/
export function colorFromHex(color: string) {
if (color.substring(0, 1) === '#') {
export function colorFromHex(color: string): string {
if (color !== '' && color.substring(0, 1) === '#') {
color = color.substring(1, 7)
}

View File

@ -0,0 +1,15 @@
import {useAuthStore} from '@/stores/auth'
import FlatpickrLanguages from 'flatpickr/dist/l10n'
import type { CustomLocale, key } from 'flatpickr/dist/types/locale'
export function getFlatpickrLanguage(): CustomLocale {
const authStore = useAuthStore()
const lang = authStore.settings.language
const langPair = lang.split('-')
let language = FlatpickrLanguages[lang === 'vi-vn' ? 'vn' : 'en']
if (langPair.length > 0 && FlatpickrLanguages[langPair[0] as key] !== undefined) {
language = FlatpickrLanguages[langPair[0] as key]
}
language.firstDayOfWeek = authStore.settings.weekStart ?? language.firstDayOfWeek
return language
}

View File

@ -0,0 +1,39 @@
import {createRandomID} from '@/helpers/randomId'
import tippy from 'tippy.js'
import {nextTick} from 'vue'
import {eventToHotkeyString} from '@github/hotkey'
export default function inputPrompt(pos: ClientRect, oldValue: string = ''): Promise<string> {
return new Promise((resolve) => {
const id = 'link-input-' + createRandomID()
const linkPopup = tippy('body', {
getReferenceClientRect: () => pos,
appendTo: () => document.body,
content: `<div><input class="input" placeholder="URL" id="${id}" value="${oldValue}"/></div>`,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'top-start',
allowHTML: true,
})
linkPopup[0].show()
nextTick(() => document.getElementById(id)?.focus())
document.getElementById(id)?.addEventListener('keydown', event => {
const hotkeyString = eventToHotkeyString(event)
if (hotkeyString !== 'Enter') {
return
}
const url = event.target.value
resolve(url)
linkPopup[0].hide()
})
})
}

View File

@ -1,16 +1,23 @@
import {createRandomID} from '@/helpers/randomId'
import type {IProvider} from '@/types/IProvider'
import {parseURL} from 'ufo'
export function getRedirectUrlFromCurrentFrontendPath(provider: IProvider): string {
// We're not using the redirect url provided by the server to allow redirects when using the electron app.
// The implications are not quite clear yet hence the logic to pass in another redirect url still exists.
const url = parseURL(window.location.href)
return `${url.protocol}//${url.host}/auth/openid/${provider.key}`
}
export const redirectToProvider = (provider: IProvider) => {
// We're not using the redirect url provided by the server to allow redirects when using the electron app.
// The implications are not quite clear yet hence the logic to pass in another redirect url still exists.
const redirectUrl = `${window.location.href.replace('/login', '')}/auth/openid/`
console.log({provider})
const redirectUrl = getRedirectUrlFromCurrentFrontendPath(provider)
const state = createRandomID(24)
localStorage.setItem('state', state)
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}${provider.key}&response_type=code&scope=openid email profile&state=${state}`
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=openid email profile&state=${state}`
}
export const redirectToProviderOnLogout = (provider: IProvider) => {
if (provider.logoutUrl.length > 0) {

View File

@ -15,34 +15,32 @@ interface dateFoundResult {
const monthsRegexGroup = '(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)'
function matchesDateExpr(text: string, dateExpr: string): boolean {
return text.match(new RegExp('(^| )' + dateExpr, 'g')) !== null
return text.match(new RegExp('(^| )' + dateExpr, 'gi')) !== null
}
export const parseDate = (text: string, now: Date = new Date()): dateParseResult => {
const lowerText: string = text.toLowerCase()
if (matchesDateExpr(lowerText, 'today')) {
if (matchesDateExpr(text, 'today')) {
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('today')), 'today')
}
if (matchesDateExpr(lowerText, 'tomorrow')) {
if (matchesDateExpr(text, 'tomorrow')) {
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('tomorrow')), 'tomorrow')
}
if (matchesDateExpr(lowerText, 'next monday')) {
if (matchesDateExpr(text, 'next monday')) {
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('nextMonday')), 'next monday')
}
if (matchesDateExpr(lowerText, 'this weekend')) {
if (matchesDateExpr(text, 'this weekend')) {
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('thisWeekend')), 'this weekend')
}
if (matchesDateExpr(lowerText, 'later this week')) {
if (matchesDateExpr(text, 'later this week')) {
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('laterThisWeek')), 'later this week')
}
if (matchesDateExpr(lowerText, 'later next week')) {
if (matchesDateExpr(text, 'later next week')) {
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('laterNextWeek')), 'later next week')
}
if (matchesDateExpr(lowerText, 'next week')) {
if (matchesDateExpr(text, 'next week')) {
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('nextWeek')), 'next week')
}
if (matchesDateExpr(lowerText, 'next month')) {
if (matchesDateExpr(text, 'next month')) {
const date: Date = new Date()
date.setDate(1)
date.setMonth(date.getMonth() + 1)
@ -52,7 +50,7 @@ export const parseDate = (text: string, now: Date = new Date()): dateParseResult
return addTimeToDate(text, date, 'next month')
}
if (matchesDateExpr(lowerText, 'end of month')) {
if (matchesDateExpr(text, 'end of month')) {
const curDate: Date = new Date()
const date: Date = new Date(curDate.getFullYear(), curDate.getMonth() + 1, 0)
date.setHours(calculateNearestHours(date))
@ -70,7 +68,7 @@ export const parseDate = (text: string, now: Date = new Date()): dateParseResult
parsed = getDayFromText(text)
if (parsed.date !== null) {
const month = getMonthFromText(text, parsed.date)
return addTimeToDate(text, month.date, parsed.foundText)
return addTimeToDate(month.newText, month.date, parsed.foundText)
}
parsed = getDateFromTextIn(text, now)
@ -123,7 +121,7 @@ const addTimeToDate = (text: string, date: Date, previousMatch: string | null):
const replace = results !== null ? results[0] : previousMatch
return {
newText: replaceAll(text, replace, ''),
newText: replaceAll(text, replace, '').trim(),
date: date,
}
}

View File

@ -762,7 +762,7 @@
"empty": "Noch keine Beschreibung vorhanden."
},
"assignee": {
"placeholder": "Tippe, um eine:n Benutzer:in zuzuweisen …",
"placeholder": "Tippe, um jemanden zuzuweisen …",
"selectPlaceholder": "Diese:n Benutzer:in zuweisen",
"assignSuccess": "Der:die Benutzer:in wurde erfolgreich zugewiesen.",
"unassignSuccess": "Benutzer:innenzuweisung aufgehoben."

View File

@ -160,7 +160,7 @@
"expired": "Žeton je potekel pred {ago}.",
"tokenCreatedSuccess": "Tu je vaš novi API žeton: {token}",
"tokenCreatedNotSeeAgain": "Shranite ga na varno mesto, ker ga ne boste več videli!",
"selectAll": "Select all",
"selectAll": "Izberi vse",
"delete": {
"header": "Izbriši ta žeton",
"text1": "Ali ste prepričani, da želite izbrisati žeton \"{token}\"?",

View File

@ -5,7 +5,6 @@ import type {ILabel} from '@/modelTypes/ILabel'
import type {IUser} from '@/modelTypes/IUser'
import {colorIsDark} from '@/helpers/color/colorIsDark'
import {getRandomColorHex} from '@/helpers/color/randomColor'
export default class LabelModel extends AbstractModel<ILabel> implements ILabel {
id = 0
@ -24,15 +23,21 @@ export default class LabelModel extends AbstractModel<ILabel> implements ILabel
constructor(data: Partial<ILabel> = {}) {
super()
this.assignData(data)
if (this.hexColor === '') {
this.hexColor = getRandomColorHex()
}
if (this.hexColor.substring(0, 1) !== '#') {
if (this.hexColor !== '' && !this.hexColor.startsWith('#') && !this.hexColor.startsWith('var(')) {
this.hexColor = '#' + this.hexColor
}
this.textColor = colorIsDark(this.hexColor) ? '#4a4a4a' : '#fff'
if (this.hexColor === '') {
this.hexColor = 'var(--grey-200)'
this.textColor = 'var(--grey-800)'
} else {
this.textColor = colorIsDark(this.hexColor)
// Fixed colors to avoid flipping in dark mode
? 'hsl(215, 27.9%, 16.9%)' // grey-800
: 'hsl(220, 13%, 91%)' // grey-200
}
this.createdBy = new UserModel(this.createdBy)
this.created = new Date(this.created)

View File

@ -112,6 +112,16 @@ describe('Parse Task Text', () => {
expect(result?.date?.getMonth()).toBe(tomorrow.getMonth())
expect(result?.date?.getDate()).toBe(tomorrow.getDate())
})
it('should recognize Tomorrow', () => {
const result = parseTaskText('Lorem Ipsum Tomorrow')
expect(result.text).toBe('Lorem Ipsum')
const tomorrow = new Date()
tomorrow.setDate(tomorrow.getDate() + 1)
expect(result?.date?.getFullYear()).toBe(tomorrow.getFullYear())
expect(result?.date?.getMonth()).toBe(tomorrow.getMonth())
expect(result?.date?.getDate()).toBe(tomorrow.getDate())
})
it('should recognize next monday', () => {
const result = parseTaskText('Lorem Ipsum next monday')
@ -441,7 +451,7 @@ describe('Parse Task Text', () => {
'06/08/2021': '2021-6-8',
'6/7/21': '2021-6-7',
'27/07/2021,': null,
'2021/07/06,': '2021-7-6',
'2021/07/06': '2021-7-6',
'2021-07-06': '2021-7-6',
'27 jan': '2022-1-27',
'27/1': '2022-1-27',
@ -449,39 +459,52 @@ describe('Parse Task Text', () => {
'16/12': '2021-12-16',
'01/27': '2022-1-27',
'1/27': '2022-1-27',
'Jan 27': '2022-1-27',
'jan 27': '2022-1-27',
'Jan 27': '2022-1-27',
'feb 21': '2022-2-21',
'Feb 21': '2022-2-21',
'mar 21': '2022-3-21',
'Mar 21': '2022-3-21',
'apr 21': '2022-4-21',
'Apr 21': '2022-4-21',
'may 21': '2022-5-21',
'May 21': '2022-5-21',
'jun 21': '2022-6-21',
'Jun 21': '2022-6-21',
'jul 21': '2021-7-21',
'Jul 21': '2021-7-21',
'aug 21': '2021-8-21',
'Aug 21': '2021-8-21',
'sep 21': '2021-9-21',
'Sep 21': '2021-9-21',
'oct 21': '2021-10-21',
'Oct 21': '2021-10-21',
'nov 21': '2021-11-21',
'Nov 21': '2021-11-21',
'dec 21': '2021-12-21',
'Dec 21': '2021-12-21',
} as Record<string, string | null>
for (const c in cases) {
it(`should parse '${c}' as '${cases[c]}' with the date at the end`, () => {
const {date} = getDateFromText(`Lorem Ipsum ${c}`, now)
const {date, foundText} = getDateFromText(`Lorem Ipsum ${c}`, now)
if (date === null && cases[c] === null) {
expect(date).toBeNull()
return
}
expect(`${date?.getFullYear()}-${date?.getMonth() + 1}-${date?.getDate()}`).toBe(cases[c])
expect(foundText.trim()).toBe(c)
})
it(`should parse '${c}' as '${cases[c]}' with the date at the beginning`, () => {
const {date} = getDateFromText(`${c} Lorem Ipsum`, now)
const {date, foundText} = getDateFromText(`${c} Lorem Ipsum`, now)
if (date === null && cases[c] === null) {
expect(date).toBeNull()
return
}
expect(`${date?.getFullYear()}-${date?.getMonth() + 1}-${date?.getDate()}`).toBe(cases[c])
expect(foundText.trim()).toBe(c)
})
}
})
@ -532,6 +555,20 @@ describe('Parse Task Text', () => {
expect(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`).toBe(cases[c])
})
}
it('should replace the text in title case', () => {
const {date, newText} = parseDate('Some task Mar 8th', now)
expect(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`).toBe('2021-3-8 12:0')
expect(newText).toBe('Some task')
})
it('should replace the text in lowercase', () => {
const {date, newText} = parseDate('Some task mar 8th', now)
expect(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`).toBe('2021-3-8 12:0')
expect(newText).toBe('Some task')
})
})
})

View File

@ -106,6 +106,7 @@ export function useSavedFilter(projectId?: MaybeRef<IProject['id']>) {
const response = await filterService.get(filter.value)
response.filters = objectToSnakeCase(response.filters)
filter.value = response
await validateTitleField()
}, {immediate: true})
async function createFilter() {

View File

@ -9,7 +9,11 @@ import UserSettingsService from '@/services/userSettings'
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
import {setModuleLoading} from '@/stores/helper'
import {success} from '@/message'
import {redirectToProvider, redirectToProviderOnLogout} from '@/helpers/redirectToProvider'
import {
getRedirectUrlFromCurrentFrontendPath,
redirectToProvider,
redirectToProviderOnLogout,
} from '@/helpers/redirectToProvider'
import {AUTH_TYPES, type IUser} from '@/modelTypes/IUser'
import type {IUserSettings} from '@/modelTypes/IUserSettings'
import router from '@/router'
@ -17,6 +21,7 @@ import {useConfigStore} from '@/stores/config'
import UserSettingsModel from '@/models/userSettings'
import {MILLISECONDS_A_SECOND} from '@/constants/date'
import {PrefixMode} from '@/modules/parseTaskText'
import type {IProvider} from '@/types/IProvider'
function redirectToProviderIfNothingElseIsEnabled() {
const {auth} = useConfigStore()
@ -180,8 +185,12 @@ export const useAuthStore = defineStore('auth', () => {
const HTTP = HTTPFactory()
setIsLoading(true)
const {auth} = useConfigStore()
const fullProvider: IProvider = auth.openidConnect.providers.find((p: IProvider) => p.key === provider)
const data = {
code: code,
redirect_url: getRedirectUrlFromCurrentFrontendPath(fullProvider),
}
// Delete an eventually preexisting old token

View File

@ -67,7 +67,7 @@ export const useLabelStore = defineStore('label', () => {
}
function setLabel(label: ILabel) {
labels.value[label.id] = label
labels.value[label.id] = {...label}
update(label)
}

View File

@ -29,6 +29,7 @@ import {useBaseStore} from '@/stores/base'
import ProjectUserService from '@/services/projectUsers'
import {useAuthStore} from '@/stores/auth'
import TaskCollectionService from '@/services/taskCollection'
import {getRandomColorHex} from '@/helpers/color/randomColor'
interface MatchedAssignee extends IUser {
match: string,
@ -337,7 +338,10 @@ export const useTaskStore = defineStore('task', () => {
let label = validateLabel(Object.values(labelStore.labels), labelTitle)
if (typeof label === 'undefined') {
// label not found, create it
const labelModel = new LabelModel({title: labelTitle})
const labelModel = new LabelModel({
title: labelTitle,
hexColor: getRandomColorHex(),
})
label = await labelStore.createLabel(labelModel)
}
return label

View File

@ -256,6 +256,13 @@
--card-border-color: var(--grey-200);
--logo-text-color: hsl(180, 1%, 15%);
// Code colors
--code-variable: #da2222;
--code-literal: #fd8a09;
--code-symbol: #0ead69;
--code-section: #3a86ff;
--code-keyword: #8338ec;
&.dark {
@media screen {
// Light mode colours reversed for dark mode
@ -311,6 +318,13 @@
--scheme-invert: var(--grey-900);
--scheme-invert-bis: var(--grey-900);
--scheme-invert-ter: var(--grey-800);
// Code colors
--code-variable: #f98181;
--code-literal: #fbbc88;
--code-symbol: #b9f18d;
--code-section: #faf594;
--code-keyword: #70cff8;
}
}
}

View File

@ -150,8 +150,8 @@ function deleteLabel(label: ILabel) {
}
function editLabelSubmit() {
return labelStore.updateLabel(labelEditLabel.value)
}
return labelStore.updateLabel(labelEditLabel.value)
}
function editLabel(label: ILabel) {
if (label.createdBy.id !== userInfo.value.id) {

View File

@ -35,7 +35,7 @@
</template>
<script setup lang="ts">
import {computed, ref} from 'vue'
import {computed, onBeforeMount, ref} from 'vue'
import {useI18n} from 'vue-i18n'
import {useRouter} from 'vue-router'
@ -46,6 +46,7 @@ import LabelModel from '@/models/label'
import {useLabelStore} from '@/stores/labels'
import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
import {getRandomColorHex} from '@/helpers/color/randomColor'
const router = useRouter()
@ -55,6 +56,8 @@ useTitle(() => t('label.create.title'))
const labelStore = useLabelStore()
const label = ref(new LabelModel())
onBeforeMount(() => label.value.hexColor = getRandomColorHex())
const showError = ref(false)
const loading = computed(() => labelStore.isLoading)

View File

@ -54,7 +54,7 @@ import {useI18n} from 'vue-i18n'
import type {RouteLocationNormalized} from 'vue-router'
import {useBaseStore} from '@/stores/base'
import {useAuthStore} from '@/stores/auth'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
import Foo from '@/components/misc/flatpickr/Flatpickr.vue'
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
@ -126,16 +126,13 @@ const flatPickerDateRange = computed<Date[]>({
const initialDateRange = [filters.value.dateFrom, filters.value.dateTo]
const {t} = useI18n({useScope: 'global'})
const authStore = useAuthStore()
const flatPickerConfig = computed<Options>(() => ({
altFormat: t('date.altFormatShort'),
altInput: true,
defaultDate: initialDateRange,
enableTime: false,
mode: 'range',
locale: {
firstDayOfWeek: authStore.settings.weekStart,
},
locale: getFlatpickrLanguage(),
}))
</script>

View File

@ -179,7 +179,23 @@ watch(
if (projectId < 0) {
return
}
tasks.value = tasks.value.filter(t => typeof t.relatedTasks?.parenttask === 'undefined')
const tasksById = {}
tasks.value.forEach(t => tasksById[t.id] = true)
tasks.value = tasks.value.filter(t => {
if (typeof t.relatedTasks?.parenttask === 'undefined') {
return true
}
// If the task is a subtask, make sure the parent task is available in the current view as well
for (const pt of t.relatedTasks.parenttask) {
if(typeof tasksById[pt.id] === 'undefined') {
return true
}
}
return false
})
},
)
@ -284,7 +300,7 @@ function prepareFiltersAndLoadTasks() {
sortByParam.value = {}
sortByParam.value[ALPHABETICAL_SORT] = 'asc'
}
loadTasks()
}
</script>

View File

@ -10,14 +10,12 @@
{{ $t('project.delete.text1') }}
</p>
<p>
<strong v-if="totalTasks !== null" class="has-text-white">
{{
totalTasks > 0 ? $t('project.delete.tasksToDelete', {count: totalTasks}) : $t('project.delete.noTasksToDelete')
}}
</strong>
<Loading v-else class="is-loading-small" variant="default"/>
<p class="has-text-weight-bold" v-if="totalTasks !== null">
{{
totalTasks > 0 ? $t('project.delete.tasksToDelete', {count: totalTasks}) : $t('project.delete.noTasksToDelete')
}}
</p>
<Loading v-else class="is-loading-small" variant="default"/>
<p>
{{ $t('misc.cannotBeUndone') }}

View File

@ -197,7 +197,7 @@ function validateSelectedEvents() {
<table
class="table has-actions is-striped is-hoverable is-fullwidth"
v-if="webhooks"
v-if="webhooks?.length > 0"
>
<thead>
<tr>

View File

@ -37,12 +37,17 @@
{{ $t('task.attributes.assignees') }}
</div>
<edit-assignees
:disabled="!canWrite"
v-if="canWrite"
:project-id="task.projectId"
:task-id="task.id"
:ref="e => setFieldRef('assignees', e)"
v-model="task.assignees"
/>
<assignee-list
v-else
:assignees="task.assignees"
class="mt-2"
/>
</div>
<CustomTransition name="flash-background" appear>
<div class="column" v-if="activeFields.priority">
@ -513,6 +518,7 @@ import {useProjectStore} from '@/stores/projects'
import {TASK_REPEAT_MODES} from '@/types/IRepeatMode'
import {useAuthStore} from '@/stores/auth'
import {playPopSound} from '@/helpers/playPop'
import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
const {
taskId,

View File

@ -10,9 +10,9 @@ import {MILLISECONDS_A_DAY} from '@/constants/date'
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
import {useI18n} from 'vue-i18n'
import {useAuthStore} from '@/stores/auth'
import Message from '@/components/misc/message.vue'
import type {IApiToken} from '@/modelTypes/IApiToken'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const service = new ApiTokenService()
const tokens = ref<IApiToken[]>([])
@ -32,18 +32,16 @@ const showDeleteModal = ref<boolean>(false)
const tokenToDelete = ref<IApiToken>()
const {t} = useI18n()
const authStore = useAuthStore()
const now = new Date()
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
locale: {
firstDayOfWeek: authStore.settings.weekStart,
},
locale: getFlatpickrLanguage(),
minDate: now,
}))