Compare commits

...

234 Commits

Author SHA1 Message Date
simon1506 8a9a5576c6
fix: no drag delay when using mouse on touch device 2021-09-13 18:26:56 +02:00
dpschen 50c1a2e4d5 feat simplify taskList mixin (#728)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#728
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 14:39:18 +00:00
dpschen 07a6a31f47 chore: move constants in folder (#732)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#732
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 14:21:33 +00:00
dpschen 0295113f50 feat: use store getters to check auth (#731)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#731
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 13:04:00 +00:00
dpschen b5df941e39 chore: define default filters and params at one location (#721)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#721
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 13:02:52 +00:00
konrad 077fe264f0 fix: use date-fns for gantt years (#734)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#734
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-09-10 12:58:23 +00:00
dpschen dae441a373 feat: simplify heading blur logic (#727)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#727
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 12:57:59 +00:00
dpschen 0376ef53e3 fix: remove attachment by id (#725)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#725
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-10 12:50:41 +00:00
renovate 7ab308b846 chore(deps): update workbox monorepo to v6.3.0 (#730)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#730
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-10 12:49:50 +00:00
renovate 33447c4a09 chore(deps): update dependency sass to v1.39.2 (#733)
Reviewed-on: vikunja/frontend#733
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-10 11:13:54 +00:00
dpschen d0e46e59e8 chore: make method event independent (#719)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#719
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-09 22:25:08 +00:00
dpschen 0ed3cf2553 feat: import bulma utilities global (#718)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#718
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-09 22:24:08 +00:00
renovate 33f1480284 chore(deps): update dependency esbuild to v0.12.26 (#729)
Reviewed-on: vikunja/frontend#729
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-09 16:17:12 +00:00
dpschen 432c6babf2 feat: use computed for api domain (#722)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#722
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 21:48:30 +00:00
dpschen 96ef926dde chore: create progress dots dynamically (#715)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#715
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 21:45:28 +00:00
dpschen 87c70cec0e chore: define default label background color once (#713)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#713
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 21:44:56 +00:00
renovate 4689de7f1f fix(deps): update dependency marked to v3.0.3 (#726)
Reviewed-on: vikunja/frontend#726
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 21:19:21 +00:00
renovate b0f616d784 [skip ci] Updated translations via Crowdin 2021-09-08 20:36:21 +00:00
renovate 11b5d0574d chore(deps): update dependency vite to v2.5.6 (#723)
Reviewed-on: vikunja/frontend#723
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 20:31:09 +00:00
kolaente 134a09c9f2
Fix download export user data title 2021-09-08 22:19:45 +02:00
kolaente c7c3ef79be
0.18.1 release preparations 2021-09-08 19:49:23 +02:00
kolaente 2bae8e95e5
Fix task attributes overridden when saving the task title with enter 2021-09-08 19:37:08 +02:00
dpschen c4095327ad feat: make it possible to fake online state via dev env (#720)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#720
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 16:30:14 +00:00
dpschen c9631c1e71 fix: call to /null from background image (#714)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#714
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 16:28:26 +00:00
dpschen 4fc8858c64 fix: kanban-card mutatation violation (#712)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#712
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-09-08 16:28:13 +00:00
kolaente 9cee720ac9
Fix sort order for table view 2021-09-08 18:13:02 +02:00
kolaente 6f89863c81
Fix missing translation when creating a new task on the kanban board 2021-09-08 17:49:10 +02:00
kolaente debdc83f1b
Fix data export download progress 2021-09-08 17:33:58 +02:00
renovate 033b30d6cd Update dependency @4tw/cypress-drag-drop to v2 (#711)
Reviewed-on: vikunja/frontend#711
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 14:27:47 +00:00
renovate c23d3c1488 Update dependency jest to v27.1.1 (#716)
Reviewed-on: vikunja/frontend#716
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 14:27:24 +00:00
renovate 4f305b28fd Update dependency vite to v2.5.5 (#709)
Reviewed-on: vikunja/frontend#709
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 05:43:13 +00:00
renovate 33926a2d11 Update dependency vite to v2.5.4 (#708)
Reviewed-on: vikunja/frontend#708
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-07 17:44:41 +00:00
kolaente 332dbc1598
Fix rearranging tasks in a kanban bucket when its limit was reached 2021-09-07 18:38:53 +02:00
renovate 28a4b1c533 Update dependency vite-plugin-vue2 to v1.8.2 (#707)
Reviewed-on: vikunja/frontend#707
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-07 16:17:41 +00:00
kolaente 78346f9ac6
Fix translation badge 2021-09-06 21:22:18 +02:00
renovate 8f916c275b Update typescript-eslint monorepo to v4.31.0 (#706)
Reviewed-on: vikunja/frontend#706
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-06 19:02:48 +00:00
renovate cb0e2a7bc1 Update dependency axios to v0.21.4 (#705)
Reviewed-on: vikunja/frontend#705
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-06 16:53:46 +00:00
kolaente 2d804c3af3
0.18.0 release preparations 2021-09-05 17:07:51 +02:00
renovate 6868fb134d Update dependency browserslist to v4.17.0 (#701)
Reviewed-on: vikunja/frontend#701
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-05 12:40:41 +00:00
kolaente b7ec975384
Change building latest docker image 2021-09-05 14:40:14 +02:00
kolaente 75174c2496
Switch the :latest docker image tag to contain the latest release instead of the latest unstable 2021-09-05 13:44:05 +02:00
kolaente e0c9332634
Add proofread languages to available languages 2021-09-05 13:31:53 +02:00
kolaente 7a4e568898 [skip ci] Updated translations via Crowdin 2021-09-05 11:10:28 +00:00
kolaente fb20afae92
Move translated files after downloading them 2021-09-05 13:06:45 +02:00
kolaente db0e023d35 [skip ci] Updated translations via Crowdin 2021-09-05 11:03:35 +00:00
kolaente 13f01cdc30
Add depends_on for push step 2021-09-05 13:03:15 +02:00
kolaente b4919a5662
Add depends_on for upload step 2021-09-05 13:02:43 +02:00
kolaente 240137cb79 [skip ci] Updated translations via Crowdin 2021-09-05 10:02:06 +00:00
kolaente a2e0c7e1f4
Fix git push remote to update crowdin translations 2021-09-05 12:01:53 +02:00
kolaente 2bc85a9de7
Fix setting secret for updating translations 2021-09-05 11:49:53 +02:00
kolaente 54ada3f06e
Automatically update approved translations from crowdin 2021-09-05 11:35:50 +02:00
kolaente c3aae638f7
Add more debug logs for gantt charts 2021-09-05 10:09:58 +02:00
renovate 8dc6d24003 chore(deps): update dependency axios to v0.21.3 (#700)
Reviewed-on: vikunja/frontend#700
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-04 20:12:15 +00:00
renovate 60173ebc59 chore(deps): update dependency axios to v0.21.2 (#698)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#698
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-04 19:57:09 +00:00
kolaente 388a31d95a
Fix gantt months being wrong 2021-09-04 21:56:22 +02:00
konrad f4c552a79f User Data Export and import (#699)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#699
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-09-04 19:26:38 +00:00
renovate 44bb7358b6 chore(deps): update dependency autoprefixer to v10.3.4 (#697)
Reviewed-on: vikunja/frontend#697
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-03 05:11:07 +00:00
renovate 67615a3984 chore(deps): update dependency esbuild to v0.12.25 (#696)
Reviewed-on: vikunja/frontend#696
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-02 17:59:44 +00:00
renovate 741f1773a2 chore(deps): update dependency sass to v1.39.0 (#695)
Reviewed-on: vikunja/frontend#695
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-02 05:24:51 +00:00
renovate c941b0a097 chore(deps): update dependency vite to v2.5.3 (#694)
Co-authored-by: konrad <k@knt.li>
Reviewed-on: vikunja/frontend#694
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-01 20:31:36 +00:00
renovate 3487203fa4 chore(deps): update dependency @4tw/cypress-drag-drop to v1.8.1 (#693)
Co-authored-by: konrad <k@knt.li>
Reviewed-on: vikunja/frontend#693
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-01 20:31:03 +00:00
kolaente 8b4efeeb11
Another day, another js date edge-case 2021-09-01 22:12:46 +02:00
renovate c42f8ca9f8 chore(deps): update dependency vite to v2.5.2 (#692)
Reviewed-on: vikunja/frontend#692
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-31 19:16:54 +00:00
kolaente f216797d4e
Fix parsing dates on the last day of the month 2021-08-31 20:02:54 +02:00
renovate 6a34262d41 chore(deps): update typescript-eslint monorepo to v4.30.0 (#691)
Reviewed-on: vikunja/frontend#691
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-30 18:22:27 +00:00
renovate daece57467 chore(deps): update dependency sass to v1.38.2 (#690)
Reviewed-on: vikunja/frontend#690
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-28 16:22:51 +00:00
renovate 6c4aacca1e chore(deps): update dependency cypress to v8.3.1 (#689)
Reviewed-on: vikunja/frontend#689
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-28 16:22:30 +00:00
renovate 059ee39772 chore(deps): update dependency esbuild to v0.12.24 (#688)
Reviewed-on: vikunja/frontend#688
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-27 15:26:19 +00:00
renovate 7a3aeab867 chore(deps): update dependency jest to v27.1.0 (#687)
Reviewed-on: vikunja/frontend#687
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-27 12:10:39 +00:00
renovate 5b5916b798 chore(deps): update dependency eslint-plugin-vue to v7.17.0 (#686)
Reviewed-on: vikunja/frontend#686
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-27 05:48:39 +00:00
renovate 1838d57eec chore(deps): update dependency typescript to v4.4.2 (#685)
Reviewed-on: vikunja/frontend#685
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-26 22:12:44 +00:00
renovate 1ace5c12f1 chore(deps): update dependency autoprefixer to v10.3.3 (#684)
Reviewed-on: vikunja/frontend#684
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-26 16:46:47 +00:00
renovate e6b9b9870a chore(deps): update dependency esbuild to v0.12.23 (#683)
Reviewed-on: vikunja/frontend#683
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-26 06:02:51 +00:00
renovate e65da2d217 fix(deps): update dependency marked to v3.0.2 (#682)
Reviewed-on: vikunja/frontend#682
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-25 06:00:17 +00:00
renovate f5bd9df510 chore(deps): update dependency vite-plugin-pwa to v0.11.2 (#681)
Reviewed-on: vikunja/frontend#681
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-24 21:21:33 +00:00
renovate c9a8d5f951 chore(deps): update dependency vite to v2.5.1 (#680)
Reviewed-on: vikunja/frontend#680
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-24 20:54:35 +00:00
renovate d024448402 chore(deps): update dependency sass to v1.38.1 (#679)
Reviewed-on: vikunja/frontend#679
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-24 05:13:28 +00:00
dpschen 0a8505f53c fix: vuex mutation violation from draggable (#674)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#674
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-08-23 19:24:52 +00:00
renovate 2adeb97074 chore(deps): update typescript-eslint monorepo to v4.29.3 (#676)
Reviewed-on: vikunja/frontend#676
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-23 19:21:26 +00:00
renovate 9e22e86d86 fix(deps): update dependency marked to v3.0.1 (#677)
Reviewed-on: vikunja/frontend#677
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-23 19:20:44 +00:00
dpschen 30d699df2d fix: non unique ids (#672)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#672
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-08-23 17:42:42 +00:00
dpschen 19f52eb8d1 chore: discard old font file formats (#673)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#673
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-08-23 17:42:31 +00:00
dpschen 0660129b41 feat: provide global variables in all components (#669)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#669
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-08-23 16:39:11 +00:00
dpschen ecb3924b09 chore: only import common languages (#671)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#671
Reviewed-by: konrad <konrad@kola-entertainments.de>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-08-23 16:38:29 +00:00
kolaente 0947ae9ce9
Fix list settings not being available when list backgrounds are disabled 2021-08-23 18:24:55 +02:00
renovate 9f392c275e Update dependency autoprefixer to v10.3.2 (#670)
Reviewed-on: vikunja/frontend#670
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-21 19:39:51 +00:00
renovate ef734103f1 Update dependency esbuild to v0.12.22 (#668)
Reviewed-on: vikunja/frontend#668
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-21 07:01:15 +00:00
renovate 49ab6c0a17 Update dependency esbuild to v0.12.21 (#666)
Reviewed-on: vikunja/frontend#666
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-20 20:37:18 +00:00
renovate 618125dde6 Update dependency vite-plugin-pwa to v0.11.0 (#667)
Reviewed-on: vikunja/frontend#667
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-20 20:36:55 +00:00
kolaente 65782ca971
Fix test for saving a task description 2021-08-20 19:10:45 +02:00
kolaente 9fda82839b
Fix showing an editor save button in cases where it wasn't required 2021-08-20 18:56:50 +02:00
kolaente 08a34d8a68
Make saving a text edit a button 2021-08-18 22:57:15 +02:00
renovate f8b3c2bb58 Update dependency browserslist to v4.16.8 (#664)
Reviewed-on: vikunja/frontend#664
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-18 17:08:02 +00:00
kolaente 38bc377323
Make sure highlight.js is always lazy-loaded 2021-08-17 21:17:31 +02:00
kolaente 9c6369e8d8
Make editor edit button at the bottom the default and make sure the done button stands out more 2021-08-17 21:10:32 +02:00
renovate 3a46032c63 Update dependency ts-jest to v27.0.5 (#662)
Reviewed-on: vikunja/frontend#662
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-17 13:21:47 +00:00
renovate 66a0f315b8 Update dependency sass to v1.38.0 (#661)
Reviewed-on: vikunja/frontend#661
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-17 06:01:17 +00:00
renovate a7eac8418a Update typescript-eslint monorepo to v4.29.2 (#659)
Reviewed-on: vikunja/frontend#659
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-16 21:17:01 +00:00
renovate d8d4c1dc21 Update dependency cypress to v8.3.0 (#660)
Reviewed-on: vikunja/frontend#660
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-16 21:16:25 +00:00
renovate 2dc5c22dea Update dependency vite to v2.5.0 (#658)
Reviewed-on: vikunja/frontend#658
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-16 10:36:30 +00:00
renovate 2e66d17376 Update dependency marked to v3 (#657)
Reviewed-on: vikunja/frontend#657
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-16 10:36:07 +00:00
kolaente d23fc3be74
Fix redirecting to /login for some routes 2021-08-15 18:52:31 +02:00
renovate cc8665b538 Update dependency vite-plugin-vue2 to v1.8.1 (#656)
Reviewed-on: vikunja/frontend#656
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-15 10:12:54 +00:00
kolaente 3aa316988b
Directly redirect to the openid auth provider if that's the only auth method 2021-08-15 12:02:29 +02:00
kolaente a5687d78f5
Fix changing the repeat mode of a task when no value is entered yet 2021-08-15 11:25:06 +02:00
kolaente f79f4101b6
Show errors from openid provider 2021-08-14 17:31:35 +02:00
kolaente 7bd081efe0
Fix loading & disabled state on inputs when creating a new task 2021-08-13 21:47:15 +02:00
renovate 69b626cba9 Update dependency dompurify to v2.3.1 (#655)
Reviewed-on: vikunja/frontend#655
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-13 19:12:14 +00:00
renovate 3e9a1e0166 Update dependency esbuild to v0.12.20 (#654)
Reviewed-on: vikunja/frontend#654
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-13 19:11:42 +00:00
renovate 49e532826f Update dependency @types/jest to v27.0.1 (#653)
Reviewed-on: vikunja/frontend#653
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-12 21:19:13 +00:00
kolaente bdf7af010c
Make the progress bar color lighter 2021-08-12 20:07:41 +02:00
kolaente fde931eaf4
Fix comment on different task after clicking on a task notification 2021-08-11 23:16:46 +02:00
renovate bf053051f8 Update workbox monorepo to v6.2.4 (#649)
Reviewed-on: vikunja/frontend#649
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-11 19:20:38 +00:00
konrad dc04c1b256 User account deletion (#651)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#651
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
2021-08-11 19:08:18 +00:00
renovate 328c172d40 Update dependency @types/jest to v27 (#650)
Reviewed-on: vikunja/frontend#650
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-10 22:20:38 +00:00
renovate f9529f605c Update dependency eslint-plugin-vue to v7.16.0 (#648)
Reviewed-on: vikunja/frontend#648
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-10 14:32:23 +00:00
renovate 9053c6914e Update typescript-eslint monorepo to v4.29.1 (#647)
Reviewed-on: vikunja/frontend#647
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-09 18:18:12 +00:00
renovate 0c998e1d9e Update dependency vite-plugin-vue2 to v1.8.0 (#646)
Reviewed-on: vikunja/frontend#646
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-09 16:24:00 +00:00
renovate 24d5446ce5 Update dependency vite-plugin-pwa to v0.10.0 (#644)
Reviewed-on: vikunja/frontend#644
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-09 11:46:13 +00:00
renovate 28d9f89a4d Update dependency vue-advanced-cropper to v1.8.2 (#645)
Reviewed-on: vikunja/frontend#645
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-09 10:33:59 +00:00
renovate bd2c2f3977 Update dependency esbuild to v0.12.19 (#643)
Reviewed-on: vikunja/frontend#643
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-08 07:05:31 +00:00
renovate 64137757c9 Update dependency vue-advanced-cropper to v1.8.1 (#642)
Reviewed-onvikunja/frontend#642
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-07 11:31:24 +00:00
renovate 30b83f4012 Update dependency vue-advanced-cropper to v1.8.0 (#641)
Reviewed-on: vikunja/frontend#641
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-07 08:04:00 +00:00
kolaente d21795ffd7
Make sure the task popup view takes up all the space it can on mobile 2021-08-06 23:52:55 +02:00
kolaente f4a4909ead
Fix populating task details ater updating the description 2021-08-06 23:45:46 +02:00
kolaente 187a8f5933
Small cleanups & code improvements 2021-08-06 23:34:37 +02:00
kolaente f2c29d42dd
Fix highlight.js in editor 2021-08-06 22:22:25 +02:00
renovate 5e82e75e1b Update workbox monorepo to v6.2.2 (#640)
Reviewed-on: vikunja/frontend#640
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-06 19:11:00 +00:00
kolaente 85306362d3
Fix multiselect search padding 2021-08-06 19:34:22 +02:00
kolaente 9195becd99
Fix global mutation of has tasks state 2021-08-06 19:29:22 +02:00
kolaente 5b70f8d5d7
Fix showing import tasks cta when tasks are loading 2021-08-06 19:25:17 +02:00
kolaente 3ff749976d
Fix setting delete button for newly created task comments 2021-08-06 18:49:31 +02:00
kolaente 0ce0ad1479
Cleanup drone pipeline 2021-08-06 10:26:16 +02:00
kolaente 33c34655df
Drone debug 2021-08-06 01:01:26 +02:00
kolaente f6aed0fb9f
Change desktop downstream trigger plugin with our own debug build 2021-08-06 00:49:53 +02:00
renovate f4c31a3168 Update workbox monorepo to v6.2.0 (#639)
Reviewed-on: vikunja/frontend#639
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-05 19:13:37 +00:00
renovate 4a8773d806 Update dependency esbuild to v0.12.18 (#638)
Reviewed-on: vikunja/frontend#638
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-05 19:13:09 +00:00
renovate 8edf69a342 Update dependency cypress to v8.2.0 (#637)
Reviewed-on: vikunja/frontend#637
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-04 20:08:00 +00:00
renovate f0a7a9b343 Update Font Awesome (#636)
Reviewed-on: vikunja/frontend#636
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-04 20:07:20 +00:00
renovate c5c83038ae Update dependency sass to v1.37.5 (#635)
Reviewed-on: vikunja/frontend#635
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-04 08:05:04 +00:00
kolaente 493180d442
Fix lint 2021-08-03 23:28:55 +02:00
kolaente e3787b9496
Only add a drag delay if on mobile instead of setting it to 0 2021-08-03 23:26:39 +02:00
renovate 7792cdf8bd Update dependency browserslist to v4.16.7 (#634)
Reviewed-on: vikunja/frontend#634
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-03 10:09:51 +00:00
renovate 304f55e763 Update dependency vite-plugin-pwa to v0.9.3 (#629)
Reviewed-on: vikunja/frontend#629
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-03 09:55:47 +00:00
renovate 4c79ee2372 Update dependency eslint-plugin-vue to v7.15.1 (#633)
Reviewed-on: vikunja/frontend#633
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-03 09:52:46 +00:00
renovate 5f9af2d040 Update dependency sass to v1.37.2 (#632)
Reviewed-on: vikunja/frontend#632
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-03 09:52:21 +00:00
renovate 96d673c32d Update typescript-eslint monorepo to v4.29.0 (#631)
Reviewed-on: vikunja/frontend#631
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-02 20:01:32 +00:00
renovate 40fddd9c56 Update dependency sass to v1.37.0 (#628)
Reviewed-on: vikunja/frontend#628
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-02 07:52:20 +00:00
renovate 7732c84719 Update dependency highlight.js to v11.2.0 (#630)
Reviewed-on: vikunja/frontend#630
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-02 07:19:38 +00:00
kolaente e855b1b1e5
Fix CTA spacings 2021-08-02 07:48:00 +02:00
renovate 2dd12f5dae Update dependency eslint to v7.32.0 (#627)
Reviewed-on: vikunja/frontend#627
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-31 05:53:08 +00:00
kolaente 364d42b2b9
Add confirm with enter when setting a new password 2021-07-30 14:46:00 +02:00
kolaente 362ccde425
Fix padding for kanban cards 2021-07-30 14:15:06 +02:00
kolaente 8f84594e92
TOTP UX improvements & translation fixes 2021-07-30 12:23:36 +02:00
renovate f9831f8e47 Update dependency cypress to v8.1.0 (#624)
Reviewed-on: vikunja/frontend#624
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-30 10:02:20 +00:00
renovate 9738694c32 Update dependency eslint-plugin-vue to v7.15.0 (#625)
Reviewed-on: vikunja/frontend#625
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-30 10:02:01 +00:00
renovate 81ab488eaa Update dependency esbuild to v0.12.17 (#623)
Reviewed-on: vikunja/frontend#623
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-29 17:13:56 +00:00
kolaente c323804c7c
Add drag delay on mobile 2021-07-29 13:08:38 +02:00
kolaente 3c2d89a0f6
Don't allow dragging a list when the user does not have the rights 2021-07-29 13:05:33 +02:00
kolaente 8a08a41a3c
Fix setting a task as favorite button 2021-07-29 13:00:48 +02:00
konrad c4067c7c35 PWA improvments (#622)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#622
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
2021-07-28 20:58:12 +00:00
kolaente a1e1fe4eb0
Add missing position property to list and bucket models 2021-07-28 22:46:33 +02:00
kolaente 617a6a0c21
Add making tasks favorite from the task detail view 2021-07-28 22:13:24 +02:00
renovate ccf466c31c Pin dependencies (#621)
Reviewed-on: vikunja/frontend#621
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-28 20:13:00 +00:00
konrad 3c7f8d7aa2 Reorder tasks, lists and kanban buckets (#620)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#620
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
2021-07-28 19:56:29 +00:00
renovate 39ef4b48f2 Update dependency vite to v2.4.4 (#619)
Reviewed-on: vikunja/frontend#619
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-27 14:07:28 +00:00
kolaente 53fe5738c9
Search namespaces locally only when duplicating a list 2021-07-27 15:36:02 +02:00
kolaente ce84067982
Fix llama background url 2021-07-27 15:26:47 +02:00
kolaente dcb846324d
Cleanup broken sw functions 2021-07-26 23:09:49 +02:00
renovate a9f4d0dff9 Update dependency vite-plugin-pwa to v0.8.2 (#612)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#612
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-26 20:26:53 +00:00
renovate bfc2d63b9a Update typescript-eslint monorepo to v4.28.5 (#618)
Reviewed-on: vikunja/frontend#618
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-26 19:06:24 +00:00
renovate f26529420a Update dependency @rollup/plugin-commonjs to v19.0.2 (#617)
Reviewed-on: vikunja/frontend#617
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-26 16:25:19 +00:00
renovate 66e5d03aef Pin dependencies (#616)
Reviewed-on: vikunja/frontend#616
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-26 16:25:09 +00:00
kolaente 91c86fc563
Add express for serve:dev 2021-07-26 17:06:04 +02:00
kolaente c0210bd6b4
Cleanup old vue cli config 2021-07-26 16:59:59 +02:00
kolaente 2af50c7307
Fix user name and avatar alignment in navbar 2021-07-26 15:37:33 +02:00
kolaente 34849d80b7
Fix Gantt layout overflowsing on mobile 2021-07-26 11:25:55 +02:00
kolaente fd5d331eca
Fix sorting labels
Resolves #603
2021-07-26 10:55:19 +02:00
renovate 55436661af Update dependency esbuild to v0.12.16 (#614)
Reviewed-on: vikunja/frontend#614
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-26 07:18:18 +00:00
renovate d4eab2a52c Update dependency vite to v2.4.3 (#611)
Reviewed-on: vikunja/frontend#611
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-25 21:17:18 +00:00
renovate 888bb0fb67 Update dependency vite-plugin-vue2 to v1.7.3 (#613)
Reviewed-on: vikunja/frontend#613
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-25 20:39:56 +00:00
renovate 7bcec2ba31 Update dependency esbuild to v0.12.15 (#610)
Reviewed-on: vikunja/frontend#610
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-25 20:39:24 +00:00
renovate 910d3e87a2 Pin dependencies (#608)
Reviewed-on: vikunja/frontend#608
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-25 20:38:40 +00:00
renovate 623a8aa3f7 Update dependency workbox-cli to v6.1.5 (#609)
Reviewed-on: vikunja/frontend#609
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-25 14:09:48 +00:00
kolaente 5fa03923e9
Improve chunk size 2021-07-25 16:02:49 +02:00
konrad a08306d612 Add vite (#416)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#416
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
2021-07-25 13:27:15 +00:00
kolaente 92c15b435c
Don't prefetch all i18n files 2021-07-25 12:55:31 +02:00
konrad c45911fd36 Fix date parsing parsing words with weekdays in them (#607)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#607
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
2021-07-25 10:45:17 +00:00
kolaente 2a6649d9dc
Only build a bundle for modern browsers 2021-07-25 12:45:05 +02:00
kolaente 8664c4f88c
Fix token in storage not getting renewed 2021-07-25 11:49:15 +02:00
renovate e2aca53253 Update dependency sass to v1.36.0 (#606)
Reviewed-on: vikunja/frontend#606
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-24 07:38:45 +00:00
renovate 272deaa400 Update dependency date-fns to v2.23.0 (#604)
Reviewed-on: vikunja/frontend#604
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-23 12:20:37 +00:00
konrad b547029379 Run frontend-tests with dist in ci (#605)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#605
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
2021-07-23 11:13:15 +00:00
kolaente 2429be7c72
Fix spacing for task detail view in lists with a background 2021-07-23 11:09:23 +02:00
kolaente ac9c854b24
Fix selecting a single value from multiselect
Used when moving a task to other lists among other things
2021-07-22 23:20:57 +02:00
kolaente 83b530b8ba
Sort labels alphabetically on tasks 2021-07-22 22:05:34 +02:00
kolaente fe4a8c17c8
Fix label changes appearing to be saved immediately when editing them 2021-07-22 22:03:49 +02:00
kolaente 926d7938ab
Show labels alphabetically sorted in the overview 2021-07-22 22:00:13 +02:00
kolaente cf25e96c50
Fix other values getting pushed away when creating a new one through multiselect 2021-07-22 21:57:33 +02:00
kolaente 028dbf27ee
Fix table headers wrapping in table view 2021-07-22 21:51:34 +02:00
kolaente 5fd7e3f708
Fix table view scrolling on mobile 2021-07-22 21:48:05 +02:00
renovate caf817f25d Update dependency ts-jest to v27.0.4 (#602)
Reviewed-on: vikunja/frontend#602
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-21 14:28:26 +00:00
kolaente 5913f78dc7
Hide keyboard shortcuts indicator on mobile 2021-07-21 00:05:11 +02:00
kolaente cc53796f1b
Decrease page padding on task detail page 2021-07-21 00:03:41 +02:00
kolaente 2e2887c2ad
Change menu hamburger icon 2021-07-20 23:57:55 +02:00
kolaente 3c548b2153
Fix labels list in saved filter spacing 2021-07-20 22:46:39 +02:00
kolaente 2838ec6148
Fix setting filters for reminders 2021-07-20 22:45:10 +02:00
kolaente 7b16928d81
Fix loading labels when editing a saved filter 2021-07-20 22:42:34 +02:00
kolaente 2779cfc140
Fix not reloading tasks of a saved filter after editing it 2021-07-20 22:16:44 +02:00
kolaente d81b4117f5
Fix quick actions not working when nonexisting lists where left over in history 2021-07-20 18:03:38 +02:00
renovate 176c6462bb Update dependency cypress to v8 (#601)
Reviewed-on: vikunja/frontend#601
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-20 07:35:02 +00:00
renovate a127ac5f66 Pin dependencies (#599)
Reviewed-on: vikunja/frontend#599
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-19 20:27:31 +00:00
renovate aa44082606 Update typescript-eslint monorepo to v4.28.4 (#600)
Reviewed-on: vikunja/frontend#600
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-19 19:50:12 +00:00
konrad fa8492f97c Add typescript support for helper functions (#598)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#598
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
2021-07-19 18:20:49 +00:00
kolaente b812c422f9
Fix sending the user back to the list view they came from when opening a task in detail view
Resolves #589
2021-07-19 11:20:05 +02:00
kolaente 46957c389f
Fix lint 2021-07-19 11:18:22 +02:00
renovate a455fb23d1 Update dependency eslint-plugin-vue to v7.14.0 (#597)
Reviewed-on: vikunja/frontend#597
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-18 10:37:33 +00:00
renovate 27a9f6c665 Update dependency eslint to v7.31.0 (#596)
Reviewed-on: vikunja/frontend#596
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-18 08:23:11 +00:00
sytone 306a926c66 Add default list setting & creating tasks from home (#520)
Co-authored-by: sytone <github@sytone.com>
Co-authored-by: Sytone <github@sytone.com>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#520
Reviewed-by: konrad <konrad@kola-entertainments.de>
Co-authored-by: sytone <kolaente@sytone.com>
Co-committed-by: sytone <kolaente@sytone.com>
2021-07-17 21:21:46 +00:00
renovate bad5e3d0ec Update dependency vue-i18n to v8.25.0 (#595)
Co-authored-by: konrad <konrad@kola-entertainments.de>
Reviewed-on: vikunja/frontend#595
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-17 17:35:20 +00:00
kolaente 15085a7c26
Fix sass division 2021-07-17 19:14:26 +02:00
kolaente be0bc5c84f
Fix lists showing up multiple times in history 2021-07-17 19:10:05 +02:00
kolaente 0e5954cf96
Fix user test fixtures 2021-07-17 19:02:03 +02:00
kolaente 01ed17a0f0
Fix setting task favorite status in test fixtures 2021-07-13 12:18:54 +02:00
kolaente 9e42559d70
Add syncing translations to crowdin 2021-07-13 12:06:50 +02:00
kolaente f0e093b3d6
Remove logout button for link shares 2021-07-10 13:13:10 +02:00
kolaente 7fa94a9bd5
Fix loading a list when it was already partially saved in vuex 2021-07-10 12:45:36 +02:00
kolaente a6842d959b
Fix quick actions not opening 2021-07-10 12:35:29 +02:00
kolaente 20fd25e280
Add timeout to fix race condition when authenticating as a link share and renewing the token simultaneously
Related #587
2021-07-10 12:32:04 +02:00
kolaente a787f6ffc7
Save auth tokens from link shares only in memory, don't persist them to localStorage
Resolves #587
2021-07-09 20:10:57 +02:00
kolaente aebfde0c74
Fix error property already defined as a function 2021-07-09 19:10:25 +02:00
kolaente eac4f88a65
Don't load already loaded task attachments again when saving an edited task description 2021-07-09 18:04:49 +02:00
kolaente 2854d3a0c2
Fix table text alignment in task detail page
Fixes #585
2021-07-09 17:43:59 +02:00
renovate e3d7ebf1b3 Update dependency highlight.js to v11.1.0 (#582)
Reviewed-on: vikunja/frontend#582
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-07-09 08:44:34 +00:00
223 changed files with 15176 additions and 12861 deletions

View File

@ -67,7 +67,7 @@ steps:
depends_on:
- dependencies
- name: build
- name: lint
image: node:16
pull: true
environment:
@ -75,7 +75,28 @@ steps:
CYPRESS_CACHE_FOLDER: .cache/cypress/
commands:
- yarn run lint
- yarn run build
depends_on:
- dependencies
# Building in dev mode to avoid the service worker for testing
- name: build-dev
image: node:16
pull: true
environment:
YARN_CACHE_FOLDER: .cache/yarn/
CYPRESS_CACHE_FOLDER: .cache/cypress/
commands:
- yarn build:dev
depends_on:
- dependencies
- name: build-prod
image: node:16
pull: true
environment:
YARN_CACHE_FOLDER: .cache/yarn/
commands:
- yarn build --dest dist-prod
depends_on:
- dependencies
@ -88,20 +109,21 @@ steps:
- dependencies
- name: test-frontend
image: cypress/browsers:node12.18.3-chrome87-ff82
image: cypress/browsers:node14.17.0-chrome91-ff89
pull: true
environment:
CYPRESS_API_URL: http://api:3456/api/v1
CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB
YARN_CACHE_FOLDER: .cache/yarn/
CYPRESS_CACHE_FOLDER: .cache/cypress/
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 20000
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
commands:
- sed -i 's/localhost/api/g' public/index.html
- yarn serve & npx wait-on http://localhost:8080
- sed -i 's/localhost/api/g' dist-dev/index.html
- yarn serve:dist-dev & npx wait-on http://localhost:5000
- yarn test:frontend --browser chrome
depends_on:
- dependencies
- build-dev
- name: upload-test-results
image: plugins/s3:1
@ -288,7 +310,7 @@ trigger:
- push
depends_on:
- release-latest
- release-latest
steps:
- name: trigger
@ -319,7 +341,7 @@ trigger:
- "refs/tags/**"
steps:
- name: docker-latest
- name: docker-unstable
image: plugins/docker:linux-arm
pull: true
settings:
@ -328,7 +350,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/frontend
tags: latest-linux-arm
tags: unstable-linux-arm
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
@ -358,7 +380,7 @@ steps:
depends_on:
- clone
- name: docker-latest-arm64
- name: docker-unstable-arm64
image: plugins/docker:linux-arm64
pull: true
settings:
@ -367,7 +389,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/frontend
tags: latest-linux-arm64
tags: unstable-linux-arm64
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
@ -416,7 +438,7 @@ trigger:
- "refs/tags/**"
steps:
- name: docker-latest
- name: docker-unstable
image: plugins/docker:linux-amd64
pull: true
settings:
@ -425,7 +447,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/frontend
tags: latest-linux-amd64
tags: unstable-linux-amd64
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
@ -466,12 +488,12 @@ depends_on:
- docker-arm-release
steps:
- name: manifest-latest
- name: manifest-unstable
pull: always
image: plugins/manifest
settings:
tags: latest
spec: docker-manifest-latest.tmpl
tags: unstable
spec: docker-manifest-unstable.tmpl
password:
from_secret: docker_password
username:
@ -494,6 +516,23 @@ steps:
when:
ref:
- "refs/tags/**"
- name: manifest-release-latest
pull: always
image: plugins/manifest
depends_on:
- clone
settings:
tags: latest
ignore_missing: true
spec: docker-manifest.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
when:
ref:
- "refs/tags/**"
---
kind: pipeline
@ -530,7 +569,8 @@ steps:
- failure
---
kind: pipeline
name: ping-weblate
type: docker
name: update-translations
depends_on:
- build
@ -542,20 +582,51 @@ trigger:
- push
steps:
- name: update-translation-base
image: appleboy/drone-git-push
failure: ignore
- name: download
pull: always
image: jonasfranz/crowdin
settings:
branch: translations
remote: ssh://git@kolaente.dev:9022/vikunja/frontend.git
ssh_key:
from_secret: translations_branch_update_ssh_key
- name: notify-weblate
image: curlimages/curl
depends_on:
- update-translation-base
download: true
export_dir: src/i18n/lang/
ignore_branch: true
project_identifier: vikunja
environment:
WEBLATE_TOKEN:
from_secret: weblate_token
commands:
- ./ping-weblate.sh
CROWDIN_KEY:
from_secret: crowdin_key
- name: move-files
pull: always
image: bash
depends_on:
- download
commands:
- mv src/i18n/lang/*/*.json src/i18n/lang
- name: push
pull: always
image: appleboy/drone-git-push
depends_on:
- move-files
settings:
author_email: "frederik@vikunja.io"
author_name: Frederick [Bot]
branch: main
commit: true
commit_message: "[skip ci] Updated translations via Crowdin"
remote: "ssh://git@kolaente.dev:9022/vikunja/frontend.git"
ssh_key:
from_secret: translation_git_push_ssh_key
- name: upload
pull: always
image: jonasfranz/crowdin
depends_on:
- clone
settings:
files:
en.json: src/i18n/lang/en.json
ignore_branch: true
project_identifier: vikunja
environment:
CROWDIN_KEY:
from_secret: crowdin_key

22
.editorconfig Normal file
View File

@ -0,0 +1,22 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false
[*.vue]
indent_style = tab
[*.{yaml,yml}]
indent_style = space
indent_size = 2
[*.json]
indent_style = space
indent_size = 2

4
.gitignore vendored
View File

@ -1,6 +1,6 @@
.DS_Store
node_modules
/dist
/dist*
*.zip
# local env files
@ -11,6 +11,7 @@ node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
stats.html
# Editor directories and files
.idea
@ -20,6 +21,7 @@ yarn-error.log*
*.njsproj
*.sln
*.sw*
!rollup.sw.js
# Test files
cypress/screenshots

View File

@ -2,13 +2,347 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
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.18.1] - 2021-09-08
### Added
* feat: make it possible to fake online state via dev env (#720)
### Fixed
* fix: call to /null from background image (#714)
* Fix data export download progress
* fix: kanban-card mutatation violation (#712)
* Fix missing translation when creating a new task on the kanban board
* Fix rearranging tasks in a kanban bucket when its limit was reached
* Fix sort order for table view
* Fix task attributes overridden when saving the task title with enter
* Fix translation badge
### Dependency Updates
* Update dependency @4tw/cypress-drag-drop to v2 (#711)
* Update dependency axios to v0.21.4 (#705)
* Update dependency jest to v27.1.1 (#716)
* Update dependency vite-plugin-vue2 to v1.8.2 (#707)
* Update dependency vite to v2.5.4 (#708)
* Update dependency vite to v2.5.5 (#709)
* Update typescript-eslint monorepo to v4.31.0 (#706)
## [0.18.0] - 2021-09-05
### Added
* Add a button to copy an attachment url from the attachment overview
* Add collapsing kanban buckets
* Add confirm with enter when setting a new password
* Add default list setting & creating tasks from home (#520)
* Add depends_on for push step
* Add depends_on for upload step
* Add drag delay on mobile
* Add express for serve:dev
* Add filters for quick action bar
* Add frontend tests for list history
* Add making tasks favorite from the task detail view
* Add missing position property to list and bucket models
* Add more debug logs for gantt charts
* Add more global state tests (#521)
* Add proofread languages to available languages
* Add quick action bar shortcut to shortcut overview
* Add setting for the first day of the week
* Add showing version info in GUI
* Add syncing translations to crowdin
* Add timeout to fix race condition when authenticating as a link share and renewing the token simultaneously
* Add translations (#562)
* Add typescript support for helper functions (#598)
* Add vite (#416)
* Allow failure of the weblate update step
* Always set the kanban board to full width for share links
* Another day, another js date edge-case
* Automatically update approved translations from crowdin
* Break long list titles in list overview
* Preload labels and use locally stored in vuex
* PWA improvments (#622)
* Quick Actions & global search (#528)
* Quick add magic for tasks (#570)
* Reorder tasks, lists and kanban buckets (#620)
* Show last visited list on home page
* Show recently visited lists in quick actions
* Show salutation based on the time of day
* Sort labels alphabetically on tasks
* Switch the :latest docker image tag to contain the latest release instead of the latest unstable
### Changed
* Change building latest docker image
* Change desktop downstream trigger plugin with our own debug build
* Change menu hamburger icon
* Change quick add magic characters to be more familiar with the todoist ones
* Change the docker builder image to a working one on arm
* chore: discard old font file formats (#673)
* chore: only import common languages (#671)
* Cleanup broken sw functions
* Cleanup drone pipeline
* Cleanup old vue cli config
* Configure tests retries
* Decrease page padding on task detail page
* Directly redirect to the openid auth provider if that's the only auth method
* Don't allow dragging a list when the user does not have the rights
* Don't load already loaded task attachments again when saving an edited task description
* Don't prefetch all i18n files
* Don't show archived lists/namespaces in quick actions
* feat: provide global variables in all components (#669)
* Hide favorite list edit menu
* Hide keyboard shortcuts indicator on mobile
* Improve chunk size
* Improve some translations (#581)
* Improve tests
* Indicate done tasks in quick actions
* Load list background in list card
* Make editor edit button at the bottom the default and make sure the done button stands out more
* Make saving a text edit a button
* Make sure highlight.js is always lazy-loaded
* Make sure the task popup view takes up all the space it can on mobile
* Make tests less flaky
* Make the logo smaller on link shared lists
* Make the progress bar color lighter
* Move creation of new items to the bottom of the multiselect list
* Move general settings to the top
* Move translated files after downloading them
* Move weblate ping to shell script
* Only add a drag delay if on mobile instead of setting it to 0
* Only build a bundle for modern browsers
* Refactor success and error messages
* Refactor success and error notifications to prevent html in them
* Remove logout button for link shares
* Run frontend-tests with dist in ci (#605)
* Save auth tokens from link shares only in memory, don't persist them to localStorage
* Search namespaces locally only when duplicating a list
* Show errors from openid provider
* Show labels alphabetically sorted in the overview
* Small cleanups & code improvements
* TOTP UX improvements & translation fixes
### Fixed
* Fix changing the repeat mode of a task when no value is entered yet
* Fix comment on different task after clicking on a task notification
* Fix CTA spacings
* Fix date parsing parsing words with weekdays in them (#607)
* fix(deps): update dependency marked to v3.0.1 (#677)
* fix(deps): update dependency marked to v3.0.2 (#682)
* Fix error property already defined as a function
* Fix flickering pre-loaded search results when focusing the search input
* Fix Gantt layout overflowsing on mobile
* Fix gantt months being wrong
* Fix git push remote to update crowdin translations
* Fix global mutation of has tasks state
* Fix header layout for long list titles
* Fix highlight.js in editor
* Fix home page tests
* Fix keyboard shortcuts not working on the task detail page
* Fix label changes appearing to be saved immediately when editing them
* Fix labels list in saved filter spacing
* Fix lint
* Fix list archived notification mobile layout
* Fix list settings not being available when list backgrounds are disabled
* Fix lists showing up multiple times in history
* Fix llama background url
* Fix loading a list when it was already partially saved in vuex
* Fix loading & disabled state on inputs when creating a new task
* Fix loading labels when editing a saved filter
* Fix menu styles
* Fix missing background for tasks on a shared list with a background
* Fix multiselect search padding
* Fix new lists created with quick actions not showing up in the menu
* fix: non unique ids (#672)
* Fix not reloading tasks of a saved filter after editing it
* Fix not updating list name in store when changing it
* Fix other values getting pushed away when creating a new one through multiselect
* Fix padding for kanban cards
* Fix parsing dates on the last day of the month
* Fix populating task details ater updating the description
* Fix quick actions not opening
* Fix quick actions not working when nonexisting lists where left over in history
* Fix redirecting to /login for some routes
* Fix removing a namespace from state after it was deleted
* Fix resetting date filters from upcoming after viewing a task detail page (popup)
* Fix sass division
* Fix saving showing archived setting
* Fix selecting a single value from multiselect
* Fix sending openid scopes when authenticating
* Fix sending the user back to the list view they came from when opening a task in detail view
* Fix setting a task as favorite button
* Fix setting delete button for newly created task comments
* Fix setting filters for reminders
* Fix setting secret for updating translations
* Fix setting task favorite status in test fixtures
* Fix showing an editor save button in cases where it wasn't required
* Fix showing edit buttons when the user does not have the rights to use them
* Fix showing import tasks cta when tasks are loading
* Fix some translation strings
* Fix sorting labels
* Fix spacing for task detail view in lists with a background
* Fix table headers wrapping in table view
* Fix table text alignment in task detail page
* Fix table view scrolling on mobile
* Fix test for saving a task description
* Fix tests failing on thursdays
* Fix token in storage not getting renewed
* Fix translating dates
* Fix usage of / in sass
* Fix user name and avatar alignment in navbar
* Fix users not removed from the list in settings when unshared
* Fix user test fixtures
* fix: vuex mutation violation from draggable (#674)
### Dependency Updates
* chore(deps): update dependency @4tw/cypress-drag-drop to v1.8.1 (#693)
* chore(deps): update dependency autoprefixer to v10.3.3 (#684)
* chore(deps): update dependency autoprefixer to v10.3.4 (#697)
* chore(deps): update dependency axios to v0.21.2 (#698)
* chore(deps): update dependency axios to v0.21.3 (#700)
* chore(deps): update dependency cypress to v8.3.1 (#689)
* chore(deps): update dependency esbuild to v0.12.23 (#683)
* chore(deps): update dependency esbuild to v0.12.24 (#688)
* chore(deps): update dependency esbuild to v0.12.25 (#696)
* chore(deps): update dependency eslint-plugin-vue to v7.17.0 (#686)
* chore(deps): update dependency jest to v27.1.0 (#687)
* chore(deps): update dependency sass to v1.38.1 (#679)
* chore(deps): update dependency sass to v1.38.2 (#690)
* chore(deps): update dependency sass to v1.39.0 (#695)
* chore(deps): update dependency typescript to v4.4.2 (#685)
* chore(deps): update dependency vite-plugin-pwa to v0.11.2 (#681)
* chore(deps): update dependency vite to v2.5.1 (#680)
* chore(deps): update dependency vite to v2.5.2 (#692)
* chore(deps): update dependency vite to v2.5.3 (#694)
* chore(deps): update typescript-eslint monorepo to v4.29.3 (#676)
* chore(deps): update typescript-eslint monorepo to v4.30.0 (#691)
* Update dependency autoprefixer to v10.3.2 (#670)
* Update dependency browserslist to v4.16.7 (#634)
* Update dependency browserslist to v4.16.8 (#664)
* Update dependency browserslist to v4.17.0 (#701)
* Update dependency bulma to v0.9.3 (#554)
* Update dependency cypress-file-upload to v5.0.8 (#556)
* Update dependency cypress to v7.3.0 (#507)
* Update dependency cypress to v7.4.0 (#517)
* Update dependency cypress to v7.5.0 (#541)
* Update dependency cypress to v7.6.0 (#561)
* Update dependency cypress to v7.7.0 (#577)
* Update dependency cypress to v8.1.0 (#624)
* Update dependency cypress to v8.2.0 (#637)
* Update dependency cypress to v8.3.0 (#660)
* Update dependency cypress to v8 (#601)
* Update dependency date-fns to v2.22.0 (#523)
* Update dependency date-fns to v2.22.1 (#524)
* Update dependency date-fns to v2.23.0 (#604)
* Update dependency dompurify to v2.2.9 (#529)
* Update dependency dompurify to v2.3.0 (#573)
* Update dependency dompurify to v2.3.1 (#655)
* Update dependency esbuild to v0.12.15 (#610)
* Update dependency esbuild to v0.12.16 (#614)
* Update dependency esbuild to v0.12.17 (#623)
* Update dependency esbuild to v0.12.18 (#638)
* Update dependency esbuild to v0.12.19 (#643)
* Update dependency esbuild to v0.12.20 (#654)
* Update dependency esbuild to v0.12.21 (#666)
* Update dependency esbuild to v0.12.22 (#668)
* Update dependency eslint-plugin-vue to v7.10.0 (#525)
* Update dependency eslint-plugin-vue to v7.11.0 (#547)
* Update dependency eslint-plugin-vue to v7.11.1 (#548)
* Update dependency eslint-plugin-vue to v7.12.1 (#565)
* Update dependency eslint-plugin-vue to v7.13.0 (#574)
* Update dependency eslint-plugin-vue to v7.14.0 (#597)
* Update dependency eslint-plugin-vue to v7.15.0 (#625)
* Update dependency eslint-plugin-vue to v7.15.1 (#633)
* Update dependency eslint-plugin-vue to v7.16.0 (#648)
* Update dependency eslint to v7.27.0 (#514)
* Update dependency eslint to v7.28.0 (#539)
* Update dependency eslint to v7.29.0 (#555)
* Update dependency eslint to v7.30.0 (#571)
* Update dependency eslint to v7.31.0 (#596)
* Update dependency eslint to v7.32.0 (#627)
* Update dependency highlight.js to v11.0.1 (#538)
* Update dependency highlight.js to v11.1.0 (#582)
* Update dependency highlight.js to v11.2.0 (#630)
* Update dependency highlight.js to v11 (#527)
* Update dependency jest to v27.0.3 (#526)
* Update dependency jest to v27.0.4 (#535)
* Update dependency jest to v27.0.5 (#558)
* Update dependency jest to v27.0.6 (#569)
* Update dependency jest to v27 (#519)
* Update dependency marked to v2.0.4 (#510)
* Update dependency marked to v2.0.5 (#513)
* Update dependency marked to v2.0.6 (#522)
* Update dependency marked to v2.0.7 (#532)
* Update dependency marked to v2.1.0 (#552)
* Update dependency marked to v2.1.1 (#553)
* Update dependency marked to v2.1.2 (#559)
* Update dependency marked to v2.1.3 (#567)
* Update dependency marked to v3 (#657)
* Update dependency @rollup/plugin-commonjs to v19.0.2 (#617)
* Update dependency sass to v1.33.0 (#512)
* Update dependency sass to v1.34.0 (#515)
* Update dependency sass to v1.34.1 (#534)
* Update dependency sass to v1.35.0 (#550)
* Update dependency sass to v1.35.1 (#551)
* Update dependency sass to v1.35.2 (#579)
* Update dependency sass to v1.36.0 (#606)
* Update dependency sass to v1.37.0 (#628)
* Update dependency sass to v1.37.2 (#632)
* Update dependency sass to v1.37.5 (#635)
* Update dependency sass to v1.38.0 (#661)
* Update dependency ts-jest to v27.0.4 (#602)
* Update dependency ts-jest to v27.0.5 (#662)
* Update dependency @types/jest to v27.0.1 (#653)
* Update dependency @types/jest to v27 (#650)
* Update dependency vite-plugin-pwa to v0.10.0 (#644)
* Update dependency vite-plugin-pwa to v0.11.0 (#667)
* Update dependency vite-plugin-pwa to v0.8.2 (#612)
* Update dependency vite-plugin-pwa to v0.9.3 (#629)
* Update dependency vite-plugin-vue2 to v1.7.3 (#613)
* Update dependency vite-plugin-vue2 to v1.8.0 (#646)
* Update dependency vite-plugin-vue2 to v1.8.1 (#656)
* Update dependency vite to v2.4.3 (#611)
* Update dependency vite to v2.4.4 (#619)
* Update dependency vite to v2.5.0 (#658)
* Update dependency vue-advanced-cropper to v1.6.0 (#516)
* Update dependency vue-advanced-cropper to v1.7.0 (#543)
* Update dependency vue-advanced-cropper to v1.8.0 (#641)
* Update dependency vue-advanced-cropper to v1.8.1 (#642)
* Update dependency vue-advanced-cropper to v1.8.2 (#645)
* Update dependency vue-flatpickr-component to v8.1.7 (#572)
* Update dependency vue-i18n to v8.24.5 (#564)
* Update dependency vue-i18n to v8.25.0 (#595)
* Update dependency vue-router to v3.5.2 (#557)
* Update dependency wait-on to v6 (#568)
* Update dependency workbox-cli to v6.1.5 (#609)
* Update Font Awesome (#636)
* Update Node.js (#549)
* Update Node.js to v16.4.1 (#576)
* Update Node.js to v16.4.2 (#578)
* Update typescript-eslint monorepo to v4.28.4 (#600)
* Update typescript-eslint monorepo to v4.28.5 (#618)
* Update typescript-eslint monorepo to v4.29.0 (#631)
* Update typescript-eslint monorepo to v4.29.1 (#647)
* Update typescript-eslint monorepo to v4.29.2 (#659)
* Update vue monorepo to v2.6.13 (#530)
* Update vue monorepo to v2.6.14 (#540)
* Update workbox monorepo to v6.2.0 (#639)
* Update workbox monorepo to v6.2.2 (#640)
* Update workbox monorepo to v6.2.4 (#649)
* User account deletion (#651)
* User Data Export and import (#699)
## [0.17.0 - 2021-05-14]
### Added
@ -148,7 +482,8 @@ The releases aim at the api versions which is why there are missing versions.
* Make sure all arm64 build steps run in parallel
* Make sure all empty pages have a call to action
* Make sure all popups & dropdowns are animated
* Make sure attachements are only added once to the list after uploading + Make sure the attachment list shows up every time after adding an attachment
* Make sure attachements are only added once to the list after uploading + Make sure the attachment list shows up every
time after adding an attachment
* Make sure no cta's are visible while the page is loading
* Make sure the loading spinner is always visible at the end of the page
* Make the button shadow lighter
@ -675,7 +1010,7 @@ The releases aim at the api versions which is why there are missing versions.
* Hide totp settings if it is disabled server side
* Increase network timeout when building docker image
* Make sure the version includes the tag when building docker images
* #PrideMonth
* # PrideMonth
* Only renew user token on tab focus events
* Redirect the user to login page if the token expired when the tab gets focus again
* Remove title length restrictions
@ -710,7 +1045,7 @@ The releases aim at the api versions which is why there are missing versions.
## [0.13] - 2020-05-12
#### Added
#### Added
* Add docker run script to change api url on startup
* Add github token for renovate (#89)
@ -1055,6 +1390,7 @@ The releases aim at the api versions which is why there are missing versions.
* Use email instead of username when resetting a password
### Fixed
* Fixed trying to verify an email when there was none
* Fixed loading tasks when the user was not authenticated

View File

@ -4,8 +4,8 @@
[![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.17.0-brightgreen.svg)](https://dl.vikunja.io)
[![Translation](https://hosted.weblate.org/widgets/vikunja/-/frontend/svg-badge.svg)](https://hosted.weblate.org/engage/vikunja/)
[![Download](https://img.shields.io/badge/download-v0.18.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.
@ -20,21 +20,25 @@ If you find any security-related issues you don't want to disclose publicly, ple
There is a [docker image available](https://hub.docker.com/r/vikunja/api) with support for http/2 and aggressive caching enabled.
## Project setup
```
```shell
yarn install
```
### Compiles and hot-reloads for development
```
```shell
yarn run serve
```
### Compiles and minifies for production
```
```shell
yarn run build
```
### Lints and fixes files
```
```shell
yarn run lint
```

View File

@ -1,5 +1,5 @@
module.exports = {
presets: [
'@vue/app'
]
'@vue/app',
],
}

View File

@ -1,5 +1,5 @@
{
"baseUrl": "http://localhost:8080",
"baseUrl": "http://localhost:5000",
"env": {
"API_URL": "http://localhost:3456/api/v1",
"TEST_SECRET": "testingS3cr3et"

View File

@ -14,7 +14,6 @@ export class TaskFactory extends Factory {
done: false,
list_id: 1,
created_by_id: 1,
is_favorite: false,
index: '{increment}',
created: formatISO(now),
updated: formatISO(now)

View File

@ -13,7 +13,7 @@ export class UserFactory extends Factory {
id: '{increment}',
username: faker.lorem.word(10) + faker.random.uuid(),
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234
is_active: true,
status: 0,
created: formatISO(now),
updated: formatISO(now)
}

View File

@ -72,7 +72,7 @@ describe('Lists', () => {
cy.get('.namespace-container .menu.namespaces-lists .more-container .menu-list li:first-child .dropdown .dropdown-content')
.contains('Edit')
.click()
cy.get('#listtext')
cy.get('#title')
.type(`{selectall}${newListName}`)
cy.get('footer.modal-card-foot .button')
.contains('Save')
@ -253,11 +253,11 @@ describe('Lists', () => {
describe('Gantt View', () => {
it('Hides tasks with no dates', () => {
TaskFactory.create(1)
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart-container .gantt-chart .tasks')
.should('be.empty')
.should('not.contain', tasks[0].title)
})
it('Shows tasks from the current and next month', () => {
@ -436,26 +436,23 @@ describe('Lists', () => {
.should('exist')
})
it('Can drag tasks around', () => {
const tasks = TaskFactory.create(2, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
// The following test does not work. It seems like vue-smooth-dnd does not use either mousemove or dragstart
// (not sure why this actually works at all?) and as I'm planning to swap that out for vuedraggable/sortable.js
// anyway, I figured it wouldn't be worth the hassle right now.
// it('Can drag tasks around', () => {
// const tasks = TaskFactory.create(2, {
// list_id: 1,
// bucket_id: 1,
// })
// cy.visit('/lists/1/kanban')
//
// cy.get('.kanban .bucket .tasks .task')
// .contains(tasks[0].title)
// .first()
// .drag('.kanban .bucket:nth-child(2) .tasks .smooth-dnd-container.vertical')
// .trigger('mousedown', {which: 1})
// .trigger('mousemove', {clientX: 500, clientY: 0})
// .trigger('mouseup', {force: true})
// })
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.first()
.drag('.kanban .bucket:nth-child(2) .tasks .dropper div')
cy.get('.kanban .bucket:nth-child(2) .tasks')
.should('contain', tasks[0].title)
cy.get('.kanban .bucket:nth-child(1) .tasks')
.should('not.contain', tasks[0].title)
})
it('Should navigate to the task when the task card is clicked', () => {
const tasks = TaskFactory.create(5, {

View File

@ -169,7 +169,7 @@ describe('Task', () => {
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
.type('{selectall}New Description')
cy.get('.task-view .details.content.description .editor a')
.contains('Done')
.contains('Save')
.click()
cy.get('.task-view .details.content.description h3 span.is-small.has-text-success')

View File

@ -36,7 +36,6 @@ describe('User Settings', () => {
.contains('Save')
.click()
cy.wait(3000) // Wait for the request to finish
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.navbar .user .username')

View File

@ -1,3 +1,4 @@
import './commands'
import 'cypress-file-upload'
import '@4tw/cypress-drag-drop'

View File

@ -1,17 +1,17 @@
image: vikunja/frontend:latest
image: vikunja/frontend:unstable
manifests:
-
image: vikunja/frontend:latest-linux-amd64
image: vikunja/frontend:unstable-linux-amd64
platform:
architecture: amd64
os: linux
-
image: vikunja/frontend:latest-linux-arm64
image: vikunja/frontend:unstable-linux-arm64
platform:
architecture: arm64
os: linux
-
image: vikunja/frontend:latest-linux-arm
image: vikunja/frontend:unstable-linux-arm
platform:
architecture: arm
os: linux

37
index.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Vikunja</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="description" content="Vikunja (/vɪˈkuːnjə/) - The to-do app to organize your life.">
<meta name="theme-color" content="#1973ff"/>
<link rel="icon" href="/favicon.ico">
<link rel="apple-touch-icon" href="/images/icons/apple-touch-icon-180x180.png"/>
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-300.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-500.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-regular.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-regular.woff2" as="font">
</head>
<body>
<noscript>
<strong>We're sorry but Vikunja doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script>
//
// This variable points the frontend to the api.
// It has to be the full url, including the last /api/v1 part and port.
// You can change this if your api is not reachable on the same port as the frontend.
window.API_URL = 'http://localhost:3456/api/v1'
//
</script>
</body>
</html>

View File

@ -3,62 +3,76 @@
"version": "0.10.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"serve:dist": "node scripts/serve-dist.js",
"build": "vue-cli-service build --modern",
"build:report": "vue-cli-service build --report",
"lint": "vue-cli-service lint --ignore-pattern '*.test.*'",
"serve": "vite",
"serve:dist-dev": "node scripts/serve-dist.js",
"serve:dist": "vite preview",
"build": "vite build && workbox copyLibraries dist/",
"build:dev": "vite build -m development --outDir dist-dev/",
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
"cypress:open": "cypress open",
"test:unit": "jest",
"test:frontend": "cypress run"
},
"dependencies": {
"browserslist": "4.16.6",
"browserslist": "4.17.0",
"bulma": "0.9.3",
"camel-case": "4.1.2",
"copy-to-clipboard": "3.3.1",
"date-fns": "2.22.1",
"dompurify": "2.3.0",
"highlight.js": "11.0.1",
"date-fns": "2.23.0",
"dompurify": "2.3.1",
"highlight.js": "11.2.0",
"is-touch-device": "1.0.1",
"lodash": "4.17.21",
"marked": "2.1.3",
"marked": "3.0.3",
"register-service-worker": "1.7.2",
"sass": "1.35.2",
"snake-case": "3.0.4",
"verte": "0.0.12",
"vue": "2.6.14",
"vue-advanced-cropper": "1.7.0",
"vue-advanced-cropper": "1.8.2",
"vue-drag-resize": "1.5.4",
"vue-easymde": "1.4.0",
"vue-i18n": "8.24.5",
"vue-i18n": "8.25.0",
"vue-shortkey": "3.1.7",
"vue-smooth-dnd": "0.8.1",
"vuex": "3.6.2"
"vuedraggable": "2.24.3",
"vuex": "3.6.2",
"workbox-precaching": "6.3.0"
},
"devDependencies": {
"@fortawesome/fontawesome-svg-core": "1.2.35",
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@4tw/cypress-drag-drop": "2.0.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "2.0.2",
"@vue/cli": "4.5.13",
"@vue/cli-plugin-babel": "4.5.13",
"@vue/cli-plugin-eslint": "4.5.13",
"@vue/cli-plugin-pwa": "4.5.13",
"@vue/cli-service": "4.5.13",
"axios": "0.21.1",
"@types/jest": "27.0.1",
"@typescript-eslint/eslint-plugin": "4.31.0",
"@typescript-eslint/parser": "4.31.0",
"@vue/babel-preset-app": "4.5.13",
"@vue/eslint-config-typescript": "7.0.0",
"autoprefixer": "10.3.4",
"axios": "0.21.4",
"babel-eslint": "10.1.0",
"cypress": "7.7.0",
"cypress": "8.3.1",
"cypress-file-upload": "5.0.8",
"eslint": "7.30.0",
"eslint-plugin-vue": "7.13.0",
"esbuild": "0.12.26",
"eslint": "7.32.0",
"eslint-plugin-vue": "7.17.0",
"express": "4.17.1",
"faker": "5.5.3",
"jest": "27.0.6",
"sass-loader": "10.2.0",
"jest": "27.1.1",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.5.2",
"sass": "1.39.2",
"ts-jest": "27.0.5",
"typescript": "4.4.2",
"vite": "2.5.6",
"vite-plugin-pwa": "0.11.2",
"vite-plugin-vue2": "1.8.2",
"vue-flatpickr-component": "8.1.7",
"vue-notification": "1.3.20",
"vue-router": "3.5.2",
"vue-template-compiler": "2.6.14",
"wait-on": "6.0.0"
"wait-on": "6.0.0",
"workbox-cli": "6.3.0"
},
"eslintConfig": {
"root": true,
@ -67,14 +81,32 @@
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
"eslint:recommended",
"@vue/typescript"
],
"rules": {},
"rules": {
"vue/html-quotes": [
"error",
"double"
],
"quotes": [
"error",
"single"
],
"comma-dangle": [
"error",
"always-multiline"
],
"semi": [
"error",
"never"
]
},
"parserOptions": {
"parser": "babel-eslint"
"parser": "@typescript-eslint/parser"
},
"ignorePatterns": [
"*.test.js",
"*.test.*",
"cypress/*"
]
},
@ -86,13 +118,27 @@
"browserslist": [
"> 1%",
"last 2 versions",
"not ie < 11"
"not ie > 0",
"not dead",
"Firefox ESR"
],
"license": "AGPL-3.0-or-later",
"jest": {
"testPathIgnorePatterns": [
"cypress"
],
"testEnvironment": "jsdom"
}
"testEnvironment": "jsdom",
"preset": "ts-jest",
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.(js|tsx?)$": "ts-jest"
},
"moduleFileExtensions": [
"ts",
"js",
"json"
]
},
"license": "AGPL-3.0-or-later"
}

View File

@ -16,9 +16,6 @@
height="1066.6667"
viewBox="0 0 1066.6667 1066.6667"
sodipodi:docname="llama-nightscape.svg"
inkscape:export-filename="/home/konrad/www/vikunja/frontend/public/images/llama-nightscape.png"
inkscape:export-xdpi="172.8"
inkscape:export-ydpi="172.8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,36 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Vikunja</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="description" content="Vikunja (/vɪˈkuːnjə/) - The to-do app to organize your life.">
<meta name="hash" content="<%= webpack.hash %>"/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="preload" crossorigin="anonymous" href="<%= BASE_URL %>fonts/open-sans-v15-latin-700italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="<%= BASE_URL %>fonts/open-sans-v15-latin-italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="<%= BASE_URL %>fonts/quicksand-v7-latin-300.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="<%= BASE_URL %>fonts/quicksand-v7-latin-500.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="<%= BASE_URL %>fonts/quicksand-v7-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="<%= BASE_URL %>fonts/open-sans-v15-latin-regular.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="<%= BASE_URL %>fonts/open-sans-v15-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="<%= BASE_URL %>fonts/quicksand-v7-latin-regular.woff2" as="font">
</head>
<body>
<noscript>
<strong>We're sorry but Vikunja doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script>
//
// This variable points the frontend to the api.
// It has to be the full url, including the last /api/v1 part and port.
// You can change this if your api is not reachable on the same port as the frontend.
window.API_URL = 'http://localhost:3456/api/v1'
//
</script>
</body>
</html>

View File

@ -2,8 +2,8 @@ const path = require('path')
const express = require('express')
const app = express()
const p = path.join(__dirname, '..', 'dist')
const port = 8080
const p = path.join(__dirname, '..', 'dist-dev')
const port = 5000
app.use(express.static(p))
// Handle urls set by the frontend

View File

@ -1,5 +1,5 @@
<template>
<div>
<div :class="{'is-touch': isTouch}">
<div :class="{'is-hidden': !online}">
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
<div class="offline" style="height: 0;width: 0;"></div>
@ -23,18 +23,18 @@
</template>
<script>
import {mapState} from 'vuex'
import authTypes from './models/authTypes'
import {mapState, mapGetters} from 'vuex'
import isTouchDevice from 'is-touch-device'
import Notification from './components/misc/notification'
import {KEYBOARD_SHORTCUTS_ACTIVE, ONLINE} from './store/mutation-types'
import KeyboardShortcuts from './components/misc/keyboard-shortcuts'
import TopNavigation from '@/components/home/topNavigation'
import ContentAuth from '@/components/home/contentAuth'
import ContentLinkShare from '@/components/home/contentLinkShare'
import ContentNoAuth from '@/components/home/contentNoAuth'
import {setLanguage} from '@/i18n/setup'
import TopNavigation from './components/home/topNavigation'
import ContentAuth from './components/home/contentAuth'
import ContentLinkShare from './components/home/contentLinkShare'
import ContentNoAuth from './components/home/contentNoAuth'
import {setLanguage} from './i18n/setup'
import AccountDeleteService from '@/services/accountDelete'
export default {
name: 'app',
@ -50,9 +50,13 @@ export default {
this.setupOnlineStatus()
this.setupPasswortResetRedirect()
this.setupEmailVerificationRedirect()
this.setupAccountDeletionVerification()
},
beforeCreate() {
this.$store.dispatch('config/update')
.then(() => {
this.$store.dispatch('auth/checkAuth')
})
this.$store.dispatch('auth/checkAuth')
setLanguage()
@ -63,12 +67,19 @@ export default {
this.$router.push({name: 'home'})
}
},
computed: mapState({
authUser: state => state.auth.authenticated && (state.auth.info && state.auth.info.type === authTypes.USER),
authLinkShare: state => state.auth.authenticated && (state.auth.info && state.auth.info.type === authTypes.LINK_SHARE),
online: ONLINE,
keyboardShortcutsActive: KEYBOARD_SHORTCUTS_ACTIVE,
}),
computed: {
isTouch() {
return isTouchDevice()
},
...mapState({
online: ONLINE,
keyboardShortcutsActive: KEYBOARD_SHORTCUTS_ACTIVE,
}),
...mapGetters('auth', [
'authUser',
'authLinkShare',
]),
},
methods: {
setupOnlineStatus() {
this.$store.commit(ONLINE, navigator.onLine)
@ -89,6 +100,17 @@ export default {
this.$router.push({name: 'user.login'})
}
},
setupAccountDeletionVerification() {
if (typeof this.$route.query.accountDeletionConfirm !== 'undefined') {
const accountDeletionService = new AccountDeleteService()
accountDeletionService.confirm(this.$route.query.accountDeletionConfirm)
.then(() => {
this.success({message: this.$t('user.deletion.confirmSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
})
.catch(e => this.error(e))
}
},
},
}
</script>

View File

@ -1,3 +0,0 @@
{
"SW_UPDATED": "swUpdated"
}

View File

@ -1,112 +0,0 @@
/* eslint-disable no-console */
/* eslint-disable no-undef */
// Cache assets
workbox.routing.registerRoute(
// This regexp matches all files in precache-manifest
new RegExp('.+\\.(css|json|js|svg|woff2|png|html|txt|wav)$'),
new workbox.strategies.StaleWhileRevalidate(),
)
// Always send api reqeusts through the network
workbox.routing.registerRoute(
new RegExp('api\\/v1\\/.*$'),
new workbox.strategies.NetworkOnly(),
)
// This code listens for the user's confirmation to update the app.
self.addEventListener('message', (e) => {
if (!e.data) {
return
}
switch (e.data) {
case 'skipWaiting':
self.skipWaiting()
break
default:
// NOOP
break
}
})
const getBearerToken = async () => {
// we can't get a client that sent the current request, therefore we need
// to ask any controlled page for auth token
const allClients = await self.clients.matchAll()
const client = allClients.filter(client => client.type === 'window')[0]
// if there is no page in scope, we can't get any token
// and we indicate it with null value
if (!client) {
return null
}
// to communicate with a page we will use MessageChannels
// they expose pipe-like interface, where a receiver of
// a message uses one end of a port for messaging and
// we use the other end for listening
const channel = new MessageChannel()
client.postMessage({
'action': 'getBearerToken',
}, [channel.port1])
// ports support only onmessage callback which
// is cumbersome to use, so we wrap it with Promise
return new Promise((resolve, reject) => {
channel.port2.onmessage = event => {
if (event.data.error) {
console.error('Port error', event.error)
reject(event.data.error)
}
resolve(event.data.authToken)
}
})
}
// Notification action
self.addEventListener('notificationclick', function (event) {
const taskId = event.notification.data.taskId
event.notification.close()
switch (event.action) {
case 'mark-as-done':
// FIXME: Ugly as hell, but no other way of doing this, since we can't use modules
// in service workers for now.
fetch('/config.json')
.then(r => r.json())
.then(config => {
getBearerToken()
.then(token => {
fetch(`${config.VIKUNJA_API_BASE_URL}tasks/${taskId}`, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({id: taskId, done: true}),
})
.then(r => r.json())
.then(r => {
console.debug('Task marked as done from notification', r)
})
.catch(e => {
console.debug('Error marking task as done from notification', e)
})
})
})
break
case 'show-task':
clients.openWindow(`/tasks/${taskId}`)
break
}
})
workbox.core.clientsClaim()
// The precaching code provided by Workbox.
self.__precacheManifest = [].concat(self.__precacheManifest || [])
workbox.precaching.precacheAndRoute(self.__precacheManifest, {})

View File

@ -5,7 +5,7 @@
</a>
<div
:class="{'has-background': background}"
:style="{'background-image': `url(${background})`}"
:style="{'background-image': background && `url(${background})`}"
class="app-container"
>
<navigation/>
@ -44,8 +44,8 @@
<script>
import {mapState} from 'vuex'
import {CURRENT_LIST, KEYBOARD_SHORTCUTS_ACTIVE, MENU_ACTIVE} from '@/store/mutation-types'
import Navigation from '@/components/home/navigation'
import QuickActions from '@/components/quick-actions/quick-actions'
import Navigation from '@/components/home/navigation.vue'
import QuickActions from '@/components/quick-actions/quick-actions.vue'
export default {
name: 'contentAuth',

View File

@ -13,16 +13,8 @@
{{ currentList.title === '' ? $t('misc.loading') : currentList.title }}
</h1>
<div class="box has-text-left view">
<div class="logout">
<x-button @click="logout()" type="secondary">
<span>{{ $t('user.auth.logout') }}</span>
<span class="icon is-small">
<icon icon="sign-out-alt"/>
</span>
</x-button>
</div>
<router-view/>
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank">
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank" rel="noreferrer noopener nofollow">
{{ $t('misc.poweredBy') }}
</a>
</div>

View File

@ -1,7 +1,7 @@
<template>
<div class="no-auth-wrapper">
<div class="noauth-container">
<img alt="Vikunja" src="/images/logo-full.svg"/>
<img alt="Vikunja" src="/images/logo-full.svg" width="400" height="117"/>
<div class="message is-info" v-if="motd !== ''">
<div class="message-header">
<p>{{ $t('misc.info') }}</p>

View File

@ -2,7 +2,7 @@
<div :class="{'is-active': menuActive}" class="namespace-container">
<div class="menu top-menu">
<router-link :to="{name: 'home'}" class="logo">
<img alt="Vikunja" src="/images/logo-full.svg"/>
<img alt="Vikunja" src="/images/logo-full.svg" width="164" height="48"/>
</router-link>
<ul class="menu-list">
<li>
@ -49,7 +49,7 @@
</div>
<aside class="menu namespaces-lists loader-container" :class="{'is-loading': loading}">
<template v-for="n in namespaces">
<template v-for="(n, nk) in namespaces">
<div :key="n.id" class="namespace-title" :class="{'has-menu': n.id > 0}">
<span
@click="toggleLists(n.id)"
@ -73,18 +73,47 @@
</a>
<namespace-settings-dropdown :namespace="n" v-if="n.id > 0"/>
</div>
<div :key="n.id + 'child'" class="more-container" v-if="typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true">
<ul class="menu-list can-be-hidden">
<template v-for="l in n.lists">
<!-- This is a bit ugly but vue wouldn't want to let me filter this - probably because the lists
are nested inside of the namespaces makes it a lot harder.-->
<li :key="l.id" v-if="!l.isArchived">
<div
:key="n.id + 'child'"
class="more-container"
v-if="typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true"
>
<!--
NOTE: a v-model / computed setter is not possible, since the updateActiveLists function
triggered by the change needs to have access to the current namespace
-->
<draggable
:value="activeLists[nk]"
@input="(lists) => updateActiveLists(n, lists)"
:group="`namespace-${n.id}-lists`"
@start="() => drag = true"
@end="e => saveListPosition(e, nk)"
v-bind="dragOptions"
handle=".handle"
:disabled="n.id < 0"
:class="{'dragging-disabled': n.id < 0}"
>
<transition-group
type="transition"
:name="!drag ? 'flip-list' : null"
tag="ul"
class="menu-list can-be-hidden"
>
<li
v-for="l in activeLists[nk]"
:key="l.id"
class="loader-container"
:class="{'is-loading': listUpdating[l.id]}"
>
<router-link
class="list-menu-link"
:class="{'router-link-exact-active': currentList.id === l.id}"
:to="{ name: 'list.index', params: { listId: l.id} }"
tag="span"
>
<span class="icon handle">
<icon icon="grip-lines"/>
</span>
<span
:style="{ backgroundColor: l.hexColor }"
class="color-bubble"
@ -104,12 +133,12 @@
<list-settings-dropdown :list="l" v-if="l.id > 0"/>
<span class="list-setting-spacer" v-else></span>
</li>
</template>
</ul>
</transition-group>
</draggable>
</div>
</template>
</aside>
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank">
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank" rel="noreferrer noopener nofollow">
{{ $t('misc.poweredBy') }}
</a>
</div>
@ -118,27 +147,41 @@
<script>
import {mapState} from 'vuex'
import {CURRENT_LIST, MENU_ACTIVE, LOADING, LOADING_MODULE} from '@/store/mutation-types'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
import draggable from 'vuedraggable'
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
export default {
name: 'navigation',
data() {
return {
listsVisible: {},
drag: false,
dragOptions: {
animation: 100,
ghostClass: 'ghost',
},
listUpdating: {},
}
},
components: {
ListSettingsDropdown,
NamespaceSettingsDropdown,
draggable,
},
computed: {
...mapState({
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
currentList: CURRENT_LIST,
background: 'background',
menuActive: MENU_ACTIVE,
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
}),
activeLists() {
return this.namespaces.map(({lists}) => lists.filter(item => !item.isArchived))
},
},
computed: mapState({
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
currentList: CURRENT_LIST,
background: 'background',
menuActive: MENU_ACTIVE,
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
}),
beforeCreate() {
this.$store.dispatch('namespaces/loadNamespaces')
.then(namespaces => {
@ -176,6 +219,45 @@ export default {
toggleLists(namespaceId) {
this.$set(this.listsVisible, namespaceId, !this.listsVisible[namespaceId] ?? false)
},
updateActiveLists(namespace, activeLists) {
// this is a bit hacky: since we do have to filter out the archived items from the list
// for vue draggable updating it is not as simple as replacing it.
// instead we iterate over the non archived items in the old list and replace them with the ones in their new order
const lists = namespace.lists.map((item) => {
if (item.isArchived) {
return item
}
return activeLists.shift()
})
const newNamespace = {
...namespace,
lists,
}
this.$store.commit('namespaces/setNamespaceById', newNamespace)
},
saveListPosition(e, namespaceIndex) {
const listsActive = this.activeLists[namespaceIndex]
const list = listsActive[e.newIndex]
const listBefore = listsActive[e.newIndex - 1] ?? null
const listAfter = listsActive[e.newIndex + 1] ?? null
this.$set(this.listUpdating, list.id, true)
const position = calculateItemPosition(listBefore !== null ? listBefore.position : null, listAfter !== null ? listAfter.position : null)
// create a copy of the list in order to not violate vuex mutations
this.$store.dispatch('lists/updateList', {
...list,
position,
})
.catch(e => {
this.error(e)
})
.finally(() => {
this.$set(this.listUpdating, list.id, false)
})
},
},
}
</script>

View File

@ -7,8 +7,8 @@
>
<div class="navbar-brand">
<router-link :to="{name: 'home'}" class="navbar-item logo">
<img alt="Vikunja" src="/images/logo-full-pride.svg" v-if="(new Date()).getMonth() === 5"/>
<img alt="Vikunja" src="/images/logo-full.svg" v-else/>
<img width="164" height="48" alt="Vikunja" src="/images/logo-full-pride.svg" v-if="(new Date()).getMonth() === 5"/>
<img width="164" height="48" alt="Vikunja" src="/images/logo-full.svg" v-else/>
</router-link>
<a
@click="$store.commit('toggleMenu')"
@ -16,14 +16,12 @@
@shortkey="() => $store.commit('toggleMenu')"
v-shortkey="['ctrl', 'e']"
>
<icon icon="bars"></icon>
</a>
</div>
<a
@click="$store.commit('toggleMenu')"
class="menu-show-button"
>
<icon icon="bars"></icon>
</a>
<div class="list-title" ref="listTitle" :style="{'display': currentList.id ? '': 'none'}">
<template v-if="currentList.id">
@ -49,7 +47,7 @@
</a>
<notifications/>
<div class="user">
<img :src="userAvatar" alt="" class="avatar"/>
<img :src="userAvatar" alt="" class="avatar" width="40" height="40"/>
<dropdown class="is-right" ref="usernameDropdown">
<template v-slot:trigger>
<x-button
@ -69,6 +67,7 @@
:href="imprintUrl"
class="dropdown-item"
target="_blank"
rel="noreferrer noopener nofollow"
v-if="imprintUrl">
{{ $t('navigation.imprint') }}
</a>
@ -76,6 +75,7 @@
:href="privacyPolicyUrl"
class="dropdown-item"
target="_blank"
rel="noreferrer noopener nofollow"
v-if="privacyPolicyUrl">
{{ $t('navigation.privacy') }}
</a>
@ -97,11 +97,11 @@
<script>
import {mapState} from 'vuex'
import {CURRENT_LIST, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
import Rights from '@/models/rights.json'
import Update from '@/components/home/update'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown'
import Dropdown from '@/components/misc/dropdown'
import Notifications from '@/components/notifications/notifications'
import Rights from '@/models/constants/rights.json'
import Update from '@/components/home/update.vue'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import Dropdown from '@/components/misc/dropdown.vue'
import Notifications from '@/components/notifications/notifications.vue'
export default {
name: 'topNavigation',

View File

@ -8,8 +8,6 @@
</template>
<script>
import swEvents from '@/ServiceWorker/events.json'
export default {
name: 'update',
data() {
@ -20,7 +18,7 @@ export default {
}
},
created() {
document.addEventListener(swEvents.SW_UPDATED, this.showRefreshUI, {once: true})
document.addEventListener('swUpdated', this.showRefreshUI, {once: true})
if (navigator && navigator.serviceWorker) {
navigator.serviceWorker.addEventListener(

View File

@ -55,7 +55,7 @@ export default {
computed: {
showIconOnly() {
return this.icon !== '' && typeof this.$slots.default === 'undefined'
}
},
},
methods: {
click(e) {

View File

@ -26,7 +26,6 @@
<script>
import verte from 'verte'
import 'verte/dist/verte.css'
export default {
name: 'colorPicker',
@ -91,6 +90,8 @@ export default {
</script>
<style lang="scss">
@import 'verte/dist/verte.css';
.verte.is-empty {
.verte__icon {
opacity: 0;

View File

@ -137,18 +137,18 @@ export default {
},
props: {
value: {
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string'
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string',
},
chooseDateLabel: {
type: String,
default() {
return this.$t('input.datepicker.chooseDate')
}
},
},
disabled: {
type: Boolean,
default: false,
}
},
},
mounted() {
this.setDateValue(this.value)

View File

@ -1,24 +1,5 @@
<template>
<div :class="{'is-pulled-up': isEditEnabled}" class="editor">
<div class="is-pulled-right mb-4" v-if="hasPreview && isEditEnabled && !hasEditBottom">
<x-button
v-if="!isEditActive"
@click="toggleEdit"
:shadow="false"
type="secondary"
>
<icon icon="pen"/>
</x-button>
<x-button
v-else
@click="toggleEdit"
:shadow="false"
type="secondary"
>
{{ $t('input.editor.done') }}
</x-button>
</div>
<div class="editor">
<div class="clear"></div>
<vue-easymde
@ -32,24 +13,34 @@
<div class="preview content" v-html="preview" v-if="isPreviewActive && text !== ''">
</div>
<p class="has-text-centered has-text-grey is-italic" v-if="isPreviewActive && text === '' && emptyText !== ''">
<p class="has-text-centered has-text-grey is-italic" v-if="showPreviewText">
{{ emptyText }}
<template v-if="isEditEnabled">
<a @click="toggleEdit">{{ $t('input.editor.edit') }}</a>.
</template>
</p>
<ul class="actions">
<template v-if="hasEditBottom && isEditEnabled">
<ul class="actions" v-if="bottomActions.length > 0">
<template v-if="isEditEnabled && !showPreviewText && showSave">
<li>
<a v-if="!isEditActive" @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
<a v-else @click="toggleEdit">{{ $t('input.editor.done') }}</a>
<a v-else @click="toggleEdit" class="done-edit">{{ $t('misc.save') }}</a>
</li>
</template>
<li v-for="(action, k) in bottomActions" :key="k">
<a @click="action.action">{{ action.title }}</a>
</li>
</ul>
<template v-else-if="showSave">
<ul v-if="!isEditActive" class="actions">
<li>
<a @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
</li>
</ul>
<x-button v-else @click="toggleEdit" type="secondary" :shadow="false">
{{ $t('misc.save') }}
</x-button>
</template>
</div>
</template>
@ -58,6 +49,7 @@ import VueEasymde from 'vue-easymde'
import EasyMDE from 'easymde'
import marked from 'marked'
import DOMPurify from 'dompurify'
import hljs from 'highlight.js/lib/common'
import AttachmentModel from '../../models/attachment'
import AttachmentService from '../../services/attachment'
@ -96,10 +88,6 @@ export default {
isEditEnabled: {
default: true,
},
hasEditBottom: {
type: Boolean,
default: false,
},
bottomActions: {
default: () => [],
},
@ -107,6 +95,15 @@ export default {
type: String,
default: () => '',
},
showSave: {
type: Boolean,
default: false,
},
},
computed: {
showPreviewText() {
return this.isPreviewActive && this.text === '' && this.emptyText !== ''
},
},
data() {
return {
@ -117,6 +114,7 @@ export default {
preview: '',
attachmentService: null,
loadedAttachments: {},
config: {
autoDownloadFontAwesome: false,
@ -284,7 +282,7 @@ export default {
// that in the end, only one change event is triggered to the outside per change.
handleInput(val) {
// Don't bubble if the text is up to date
if(val === this.text) {
if (val === this.text) {
return
}
@ -365,17 +363,16 @@ export default {
link: (href, title, text) => {
const isLocal = href.startsWith(`${location.protocol}//${location.hostname}`)
const html = linkRenderer.call(renderer, href, title, text)
return isLocal ? html : html.replace(/^<a /, `<a target="_blank" rel="noreferrer noopener nofollow" `)
return isLocal ? html : html.replace(/^<a /, '<a target="_blank" rel="noreferrer noopener nofollow" ')
},
},
highlight: function (code, language) {
const hljs = require('highlight.js')
const validLanguage = hljs.getLanguage(language) ? language : 'plaintext'
return hljs.highlight(code, {language: validLanguage}).value
},
})
this.preview = DOMPurify.sanitize(marked(this.text), { ADD_ATTR: ['target'] })
this.preview = DOMPurify.sanitize(marked(this.text), {ADD_ATTR: ['target']})
// Since the render function is synchronous, we can't do async http requests in it.
// Therefore, we can't resolve the blob url at (markdown) compile time.
@ -392,6 +389,13 @@ export default {
const parts = img.dataset.src.substr(window.API_URL.length + 1).split('/')
const taskId = parseInt(parts[1])
const attachmentId = parseInt(parts[3])
const cacheKey = `${taskId}-${attachmentId}`
if (typeof this.loadedAttachments[cacheKey] !== 'undefined') {
img.src = this.loadedAttachments[cacheKey]
continue
}
const attachment = new AttachmentModel({taskId: taskId, id: attachmentId})
if (this.attachmentService === null) {
@ -401,6 +405,7 @@ export default {
this.attachmentService.getBlobUrl(attachment)
.then(url => {
img.src = url
this.loadedAttachments[cacheKey] = url
})
}
}
@ -452,15 +457,18 @@ export default {
<style lang="scss">
@import '../../../node_modules/highlight.js/scss/base16/equilibrium-gray-light';
@import '../../../node_modules/easymde/dist/easymde.min.css';
@import '../../styles/theme/variables/all';
.editor {
.clear {
clear: both;
}
.preview.content ul li input[type="checkbox"] {
margin-right: .5rem;
.preview.content {
margin-bottom: .5rem;
ul li input[type="checkbox"] {
margin-right: .5rem;
}
}
}
@ -539,6 +547,10 @@ ul.actions {
&, a {
color: $grey-500;
&.done-edit {
color: $primary;
}
}
a:hover {

View File

@ -4,7 +4,7 @@
:checked="checked"
:disabled="disabled"
:id="checkBoxId"
@change="updateData"
@change="(event) => updateData(event.target.checked)"
style="display: none;"
type="checkbox"/>
<label :for="checkBoxId" class="check">
@ -51,10 +51,10 @@ export default {
this.checkBoxId = 'fancycheckbox' + Math.random()
},
methods: {
updateData(e) {
this.checked = e.target.checked
this.$emit('input', this.checked)
this.$emit('change', e.target.checked)
updateData(checked) {
this.checked = checked
this.$emit('input', checked)
this.$emit('change', checked)
},
},
}

View File

@ -108,21 +108,21 @@ export default {
type: Boolean,
default() {
return false
}
},
},
// The placeholder of the search input
placeholder: {
type: String,
default() {
return ''
}
},
},
// The search results where the @search listener needs to put the results into
searchResults: {
type: Array,
default() {
return []
}
},
},
// The name of the property of the searched object to show the user.
// If empty the component will show all raw data of an entry.
@ -130,13 +130,13 @@ export default {
type: String,
default() {
return ''
}
},
},
// The object with the value, updated every time an entry is selected.
value: {
default() {
return null
}
},
},
// If true, will provide an "add this as a new value" entry which fires an @create event when clicking on it.
creatable: {
@ -150,14 +150,14 @@ export default {
type: String,
default() {
return this.$t('input.multiselect.createPlaceholder')
}
},
},
// The text shown next to an option.
selectPlaceholder: {
type: String,
default() {
return this.$t('input.multiselect.selectPlaceholder')
}
},
},
// If true, allows for selecting multiple items. v-model will be an array with all selected values in that case.
multiple: {
@ -222,7 +222,7 @@ export default {
})
},
filteredSearchResults() {
if (this.multiple && this.internalValue !== null) {
if (this.multiple && this.internalValue !== null && Array.isArray(this.internalValue)) {
return this.searchResults.filter(item => !this.internalValue.some(e => e === item))
}

View File

@ -75,9 +75,9 @@
<script>
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
import Dropdown from '@/components/misc/dropdown'
import DropdownItem from '@/components/misc/dropdown-item'
import TaskSubscription from '@/components/misc/subscription'
import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue'
import TaskSubscription from '@/components/misc/subscription.vue'
export default {
name: 'list-settings-dropdown',
@ -101,7 +101,7 @@ export default {
},
computed: {
backgroundsEnabled() {
return this.$store.state.config.enabledBackgroundProviders.length > 0
return this.$store.state.config.enabledBackgroundProviders !== null && this.$store.state.config.enabledBackgroundProviders.length > 0
},
listRoutePrefix() {
let name = 'list'

View File

@ -131,25 +131,8 @@
<div class="field">
<label class="label">{{ $t('task.attributes.labels') }}</label>
<div class="control">
<multiselect
:placeholder="$t('label.search')"
@search="findLabels"
:search-results="foundLabels"
@select="label => addLabel(label)"
label="title"
:multiple="true"
v-model="labels"
>
<template v-slot:tag="props">
<span
:style="{'background': props.item.hexColor, 'color': props.item.textColor}"
class="tag ml-2 mt-2">
<span>{{ props.item.title }}</span>
<a @click="removeLabel(props.item)" class="delete is-small"></a>
</span>
</template>
</multiselect>
<div class="control labels-list">
<edit-labels v-model="labels" @change="changeLabelFilter"/>
</div>
</div>
@ -198,17 +181,48 @@ import 'flatpickr/dist/flatpickr.css'
import {formatISO} from 'date-fns'
import differenceWith from 'lodash/differenceWith'
import PrioritySelect from '@/components/tasks/partials/prioritySelect'
import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect'
import Multiselect from '@/components/input/multiselect'
import PrioritySelect from '@/components/tasks/partials/prioritySelect.vue'
import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect.vue'
import Multiselect from '@/components/input/multiselect.vue'
import UserService from '@/services/user'
import ListService from '@/services/list'
import NamespaceService from '@/services/namespace'
import EditLabels from '@/components/tasks/partials/editLabels.vue'
// FIXME: merge with DEFAULT_PARAMS in taskList.js
const DEFAULT_PARAMS = {
sort_by: [],
order_by: [],
filter_by: [],
filter_value: [],
filter_comparator: [],
filter_include_nulls: true,
filter_concat: 'or',
s: '',
}
const DEFAULT_FILTERS = {
done: false,
dueDate: '',
requireAllFilters: false,
priority: 0,
usePriority: false,
startDate: '',
endDate: '',
percentDone: 0,
usePercentDone: false,
reminders: '',
assignees: '',
labels: '',
list_id: '',
namespace: '',
}
export default {
name: 'filters',
components: {
EditLabels,
PrioritySelect,
Fancycheckbox,
flatPickr,
@ -217,32 +231,8 @@ export default {
},
data() {
return {
params: {
sort_by: [],
order_by: [],
filter_by: [],
filter_value: [],
filter_comparator: [],
filter_include_nulls: true,
filter_concat: 'or',
s: '',
},
filters: {
done: false,
dueDate: '',
requireAllFilters: false,
priority: 0,
usePriority: false,
startDate: '',
endDate: '',
percentDone: 0,
usePercentDone: false,
reminders: '',
assignees: '',
labels: '',
list_id: '',
namespace: '',
},
params: DEFAULT_PARAMS,
filters: DEFAULT_FILTERS,
usersService: UserService,
foundusers: [],
@ -319,9 +309,12 @@ export default {
this.prepareSingleValue('percent_done', 'percentDone', 'usePercentDone', true)
this.prepareDate('reminders')
this.prepareRelatedObjectFilter('users', 'assignees')
this.prepareRelatedObjectFilter('labels', 'labels', 'label')
this.prepareRelatedObjectFilter('lists', 'list_id')
this.prepareRelatedObjectFilter('namespace')
this.prepareSingleValue('labels')
const labelIds = (typeof this.filters.labels === 'string' ? this.filters.labels : '').split(',').map(i => parseInt(i))
this.labels = (Object.values(this.$store.state.labels.labels).filter(l => labelIds.includes(l.id)) ?? [])
},
removePropertyFromFilter(propertyName) {
// Because of the way arrays work, we can only ever remove one element at once.
@ -335,7 +328,11 @@ export default {
}
}
},
setDateFilter(filterName, variableName) {
setDateFilter(filterName, variableName = null) {
if (variableName === null) {
variableName = filterName
}
// Only filter if we have a start and end due date
if (this.filters[variableName] !== '') {

View File

@ -1,16 +1,34 @@
<template>
<div class="content">
<h1>{{ $t('migrate.titleService', { name: name }) }}</h1>
<h1>{{ $t('migrate.titleService', {name: name}) }}</h1>
<p>{{ $t('migrate.descriptionDo') }}</p>
<template v-if="isMigrating === false && message === '' && lastMigrationDate === null">
<p>{{ $t('migrate.authorize', {name: name}) }}</p>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading"
:href="authUrl"
>
{{ $t('migrate.getStarted') }}
</x-button>
<template v-if="isFileMigrator">
<p>{{ $t('migrate.importUpload', {name: name}) }}</p>
<input
@change="migrate"
class="is-hidden"
ref="uploadInput"
type="file"
/>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading"
@click="$refs.uploadInput.click()"
>
{{ $t('migrate.upload') }}
</x-button>
</template>
<template v-else>
<p>{{ $t('migrate.authorize', {name: name}) }}</p>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading"
:href="authUrl"
>
{{ $t('migrate.getStarted') }}
</x-button>
</template>
</template>
<div
class="migration-in-progress-container"
@ -18,14 +36,7 @@
<div class="migration-in-progress">
<img :alt="name" :src="`/images/migration/${identifier}.png`"/>
<div class="progress-dots">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span v-for="i in progressDotsCount" :key="i" />
</div>
<img alt="Vikunja" src="/images/logo.svg">
</div>
@ -33,7 +44,7 @@
</div>
<div v-else-if="lastMigrationDate">
<p>
{{ $t('migrate.alreadyMigrated1', { name: name, date: formatDate(lastMigrationDate) }) }}<br/>
{{ $t('migrate.alreadyMigrated1', {name: name, date: formatDate(lastMigrationDate)}) }}<br/>
{{ $t('migrate.alreadyMigrated2') }}
</p>
<div class="buttons">
@ -53,17 +64,22 @@
</template>
<script>
import AbstractMigrationService from '../../services/migrator/abstractMigrationService'
import AbstractMigrationService from '../../services/migrator/abstractMigration'
import AbstractMigrationFileService from '../../services/migrator/abstractMigrationFile'
const PROGRESS_DOTS_COUNT = 8
export default {
name: 'migration',
data() {
return {
progressDotsCount: PROGRESS_DOTS_COUNT,
authUrl: '',
isMigrating: false,
lastMigrationDate: null,
message: '',
migratorAuthCode: '',
migrationService: null,
}
},
props: {
@ -75,11 +91,21 @@ export default {
type: String,
required: true,
},
isFileMigrator: {
type: Boolean,
default: false,
},
},
created() {
this.message = ''
if (this.isFileMigrator) {
this.migrationService = new AbstractMigrationFileService(this.identifier)
return
}
this.migrationService = new AbstractMigrationService(this.identifier)
this.getAuthUrl()
this.message = ''
if (typeof this.$route.query.code !== 'undefined' || location.hash.startsWith('#token=')) {
if (location.hash.startsWith('#token=')) {
@ -122,6 +148,11 @@ export default {
this.isMigrating = true
this.lastMigrationDate = null
this.message = ''
if (this.isFileMigrator) {
return this.migrateFile()
}
this.migrationService.migrate({code: this.migratorAuthCode})
.then(r => {
this.message = r.message
@ -134,6 +165,23 @@ export default {
this.isMigrating = false
})
},
migrateFile() {
if (this.$refs.uploadInput.files.length === 0) {
return
}
this.migrationService.migrate(this.$refs.uploadInput.files[0])
.then(r => {
this.message = r.message
this.$store.dispatch('namespaces/loadNamespaces')
})
.catch(e => {
this.error(e)
})
.finally(() => {
this.isMigrating = false
})
},
},
}
</script>

View File

@ -24,7 +24,7 @@
</div>
<div class="api-url-info" v-else>
<i18n path="apiConfig.signInOn">
<span class="url" v-tooltip="apiUrl"> {{ apiDomain() }} </span>
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
</i18n>
<br />
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
@ -46,23 +46,24 @@
</template>
<script>
const API_DEFAULT_PORT = 3456
export default {
name: 'apiConfig',
data() {
return {
configureApi: false,
apiUrl: '',
apiUrl: window.API_URL,
errorMsg: '',
successMsg: '',
}
},
created() {
this.apiUrl = window.API_URL
if (this.apiUrl === '') {
this.configureApi = true
}
},
methods: {
computed: {
apiDomain() {
if (window.API_URL.startsWith('/api/v1')) {
return window.location.host
@ -72,6 +73,8 @@ export default {
.split(/[/?#]/)
return urlParts[0]
},
},
methods: {
setApiUrl() {
if (this.apiUrl === '') {
return
@ -131,17 +134,17 @@ export default {
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at port 3456 and https
if (urlToCheck.port !== 3456) {
// Check if it is reachable at port API_DEFAULT_PORT and https
if (urlToCheck.port !== API_DEFAULT_PORT) {
urlToCheck.protocol = 'https:'
urlToCheck.port = 3456
urlToCheck.port = API_DEFAULT_PORT
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at :3456 and /api/v1 and https
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and https
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
@ -154,17 +157,17 @@ export default {
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at port 3456 and http
if (urlToCheck.port !== 3456) {
// Check if it is reachable at port API_DEFAULT_PORT and http
if (urlToCheck.port !== API_DEFAULT_PORT) {
urlToCheck.protocol = 'http:'
urlToCheck.port = 3456
urlToCheck.port = API_DEFAULT_PORT
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at :3456 and /api/v1 and http
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and http
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
@ -179,14 +182,14 @@ export default {
.catch(() => {
// Still not found, url is still invalid
this.successMsg = ''
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain()})
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain})
window.API_URL = oldUrl
})
.then((r) => {
if (typeof r !== 'undefined') {
// Set it + save it to local storage to save us the hoops
this.errorMsg = ''
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain()})
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain})
localStorage.setItem('API_URL', window.API_URL)
this.configureApi = false
this.apiUrl = window.API_URL

View File

@ -22,7 +22,7 @@ export default {
type: String,
required: false,
default: '',
}
},
},
}
</script>

View File

@ -2,7 +2,7 @@
<div class="notification is-danger">
<i18n path="loadingError.failed">
<a @click="() => location.reload()">{{ $t('loadingError.tryAgain') }}</a>
<a href="https://vikunja.io/contact/">{{ $t('loadingError.contact') }}</a>
<a href="https://vikunja.io/contact/" rel="noreferrer noopener nofollow" target="_blank">{{ $t('loadingError.contact') }}</a>
</i18n>
</div>
</template>

View File

@ -62,7 +62,7 @@
<script>
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
import Shortcut from '@/components/misc/shortcut'
import Shortcut from '@/components/misc/shortcut.vue'
export default {
name: 'keyboard-shortcuts',

View File

@ -1,8 +1,8 @@
<template>
<div class="legal-links">
<a :href="imprintUrl" target="_blank" v-if="imprintUrl">{{ $t('navigation.imprint') }}</a>
<a :href="imprintUrl" rel="noreferrer noopener nofollow" target="_blank" v-if="imprintUrl">{{ $t('navigation.imprint') }}</a>
<span v-if="imprintUrl && privacyPolicyUrl"> | </span>
<a :href="privacyPolicyUrl" target="_blank" v-if="privacyPolicyUrl">{{ $t('navigation.privacy') }}</a>
<a :href="privacyPolicyUrl" rel="noreferrer noopener nofollow" target="_blank" v-if="privacyPolicyUrl">{{ $t('navigation.privacy') }}</a>
</div>
</template>

View File

@ -6,6 +6,6 @@
<script>
export default {
name: 'nothing'
name: 'nothing',
}
</script>

View File

@ -14,7 +14,7 @@ export default {
keys: {
type: Array,
required: true,
}
},
},
}
</script>

View File

@ -57,7 +57,7 @@ export default {
if (this.disabled) {
return this.$t('task.subscription.subscribedThroughParent', {
entity: this.entity,
parent: this.subscription.entity
parent: this.subscription.entity,
})
}
@ -118,7 +118,7 @@ export default {
.catch(e => {
this.error(e)
})
}
},
},
}
</script>

View File

@ -1,53 +1,43 @@
<template>
<multiselect
:loading="namespaceService.loading"
:placeholder="$t('namespace.search')"
@search="findNamespaces"
:search-results="namespaces"
@select="select"
label="title"
v-model="namespace"
:search-delay="10"
/>
</template>
<script>
import NamespaceService from '../../services/namespace'
import NamespaceModel from '../../models/namespace'
import Multiselect from '@/components/input/multiselect'
import Multiselect from '@/components/input/multiselect.vue'
export default {
name: 'namespace-search',
data() {
return {
namespaceService: NamespaceService,
namespace: NamespaceModel,
namespaces: [],
query: '',
}
},
components: {
Multiselect,
},
created() {
this.namespaceService = new NamespaceService()
computed: {
namespaces() {
if (this.query === '') {
return []
}
return this.$store.state.namespaces.namespaces.filter(n => {
return !n.isArchived &&
n.id > 0 &&
n.title.toLowerCase().includes(this.query.toLowerCase())
})
},
},
methods: {
findNamespaces(query) {
if (query === '') {
this.clearAll()
return
}
this.namespaceService.getAll({}, {s: query})
.then(response => {
this.$set(this, 'namespaces', response)
})
.catch(e => {
this.error(e)
})
},
clearAll() {
this.$set(this, 'namespaces', [])
this.query = query
},
select(namespace) {
this.$emit('selected', namespace)

View File

@ -53,9 +53,9 @@
</template>
<script>
import Dropdown from '@/components/misc/dropdown'
import DropdownItem from '@/components/misc/dropdown-item'
import TaskSubscription from '@/components/misc/subscription'
import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue'
import TaskSubscription from '@/components/misc/subscription.vue'
export default {
name: 'namespace-settings-dropdown',

View File

@ -49,8 +49,8 @@
<script>
import NotificationService from '@/services/notification'
import User from '@/components/misc/user'
import names from '@/models/notificationNames.json'
import User from '@/components/misc/user.vue'
import names from '@/models/constants/notificationNames.json'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {mapState} from 'vuex'

View File

@ -62,8 +62,8 @@ import TeamModel from '@/models/team'
import {CURRENT_LIST, LOADING, LOADING_MODULE, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
import ListModel from '@/models/list'
import createTask from '@/components/tasks/mixins/createTask'
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic'
import {getHistory} from '@/modules/listHistory'
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
import {getHistory} from '../../modules/listHistory'
const TYPE_LIST = 'list'
const TYPE_TASK = 'task'
@ -127,6 +127,10 @@ export default {
...Object.values(this.$store.state.lists)])]
lists = (allLists.filter(l => {
if (typeof l === 'undefined' || l === null) {
return false
}
if (l.isArchived) {
return false
}
@ -477,7 +481,7 @@ export default {
reset() {
this.query = ''
this.selectedCmd = null
}
},
},
}
</script>

View File

@ -173,7 +173,7 @@
</template>
<script>
import rights from '../../models/rights'
import rights from '../../models/constants/rights'
import LinkShareService from '../../services/linkShare'
import LinkShareModel from '../../models/linkShare'

View File

@ -145,9 +145,9 @@ import TeamListService from '../../services/teamList'
import TeamService from '../../services/team'
import TeamModel from '../../models/team'
import rights from '../../models/rights'
import Multiselect from '@/components/input/multiselect'
import Nothing from '@/components/misc/nothing'
import rights from '../../models/constants/rights.json'
import Multiselect from '@/components/input/multiselect.vue'
import Nothing from '@/components/misc/nothing.vue'
export default {
name: 'userTeamShare',
@ -235,11 +235,11 @@ export default {
this.searchLabel = 'username'
if (this.type === 'list') {
this.typeString = `list`
this.typeString = 'list'
this.stuffService = new UserListService()
this.stuffModel = new UserListModel({listId: this.id})
} else if (this.type === 'namespace') {
this.typeString = `namespace`
this.typeString = 'namespace'
this.stuffService = new UserNamespaceService()
this.stuffModel = new UserNamespaceModel({
namespaceId: this.id,
@ -253,11 +253,11 @@ export default {
this.searchLabel = 'name'
if (this.type === 'list') {
this.typeString = `list`
this.typeString = 'list'
this.stuffService = new TeamListService()
this.stuffModel = new TeamListModel({listId: this.id})
} else if (this.type === 'namespace') {
this.typeString = `namespace`
this.typeString = 'namespace'
this.stuffService = new TeamNamespaceService()
this.stuffModel = new TeamNamespaceModel({
namespaceId: this.id,
@ -278,7 +278,7 @@ export default {
.then((r) => {
this.$set(this, 'sharables', r)
r.forEach((s) =>
this.$set(this.selectedRight, s.id, s.right)
this.$set(this.selectedRight, s.id, s.right),
)
})
.catch((e) => {

View File

@ -0,0 +1,104 @@
<template>
<div class="task-add">
<div class="field is-grouped">
<p class="control has-icons-left is-expanded">
<input
:disabled="taskService.loading"
@keyup.enter="addTask()"
class="input"
:placeholder="$t('list.list.addPlaceholder')"
type="text"
v-focus
v-model="newTaskTitle"
ref="newTaskInput"
@keyup="errorMessage = ''"
/>
<span class="icon is-small is-left">
<icon icon="tasks"/>
</span>
</p>
<p class="control">
<x-button
:disabled="newTaskTitle === '' || taskService.loading"
@click="addTask()"
icon="plus"
:loading="taskService.loading"
>
{{ $t('list.list.add') }}
</x-button>
</p>
</div>
<p class="help is-danger" v-if="errorMessage !== ''">
{{ errorMessage }}
</p>
<quick-add-magic v-if="errorMessage === ''"/>
</div>
</template>
<script>
import TaskService from '../../services/task'
import createTask from '@/components/tasks/mixins/createTask'
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
export default {
name: 'add-task',
data() {
return {
newTaskTitle: '',
taskService: TaskService,
errorMessage: '',
}
},
mixins: [
createTask,
],
components: {
QuickAddMagic,
},
created() {
this.taskService = new TaskService()
},
props: {
defaultPosition: {
type: Number,
required: false,
},
},
methods: {
addTask() {
if (this.newTaskTitle === '') {
this.errorMessage = this.$t('list.create.addTitleRequired')
return
}
this.errorMessage = ''
if (this.taskService.loading) {
return
}
this.createNewTask(this.newTaskTitle, 0, this.$store.state.auth.settings.defaultListId, this.defaultPosition)
.then(task => {
this.newTaskTitle = ''
this.$emit('taskAdded', task)
})
.catch(e => {
if (e === 'NO_LIST') {
this.errorMessage = this.$t('list.create.addListRequired')
return
}
this.error(e)
})
},
},
}
</script>
<style lang="scss" scoped>
.task-add {
margin-bottom: 0;
.button {
height: 2.5rem;
}
}
</style>

View File

@ -72,7 +72,7 @@
import ListService from '../../services/list'
import TaskService from '../../services/task'
import TaskModel from '../../models/task'
import priorities from '../../models/priorities'
import priorities from '../../models/constants/priorities'
import EditLabels from './partials/editLabels'
import Reminders from './partials/reminders'
import ColorPicker from '../input/colorPicker'
@ -100,7 +100,7 @@ export default {
Reminders,
EditLabels,
editor: () => ({
component: import(/* webpackChunkName: "editor" */ '../../components/input/editor'),
component: import('../../components/input/editor'),
loading: LoadingComponent,
error: ErrorComponent,
timeout: 60000,

View File

@ -11,7 +11,7 @@
</x-button>
</div>
<filter-popup
@change="loadTasks"
@change="loadTasks()"
:visible="showTaskFilter"
v-model="params"
/>
@ -24,18 +24,10 @@
class="month"
v-for="(m, mk) in days[yk]"
>
{{
new Date(
new Date(yk).setMonth(mk)
).toLocaleString('en-us', {month: 'long'})
}},
{{ new Date(yk).getFullYear() }}
{{ formatYear(new Date(`${yk}-${parseInt(mk) + 1}-01`)) }}
<div class="days">
<div
:class="{
today:
d.toDateString() === now.toDateString(),
}"
:class="{ today: d.toDateString() === now.toDateString() }"
:key="dk + 'day'"
:style="{ width: dayWidth + 'px' }"
class="day"
@ -196,12 +188,13 @@ import EditTask from './edit-task'
import TaskService from '../../services/task'
import TaskModel from '../../models/task'
import priorities from '../../models/priorities'
import priorities from '../../models/constants/priorities'
import PriorityLabel from './partials/priorityLabel'
import TaskCollectionService from '../../services/taskCollection'
import {mapState} from 'vuex'
import Rights from '../../models/rights.json'
import FilterPopup from '@/components/list/partials/filter-popup'
import Rights from '../../models/constants/rights.json'
import FilterPopup from '@/components/list/partials/filter-popup.vue'
import {format} from 'date-fns'
export default {
name: 'GanttChart',
@ -288,12 +281,12 @@ export default {
setDates() {
this.startDate = new Date(this.dateFrom)
this.endDate = new Date(this.dateTo)
console.debug('setDates; start date: ', this.startDate, 'end date:', this.endDate, 'date from:', this.dateFrom, 'date to:', this.dateTo)
this.dayOffsetUntilToday =
Math.floor((this.now - this.startDate) / 1000 / 60 / 60 / 24) +
1
this.dayOffsetUntilToday = Math.floor((this.now - this.startDate) / 1000 / 60 / 60 / 24) + 1
},
prepareGanttDays() {
console.debug('prepareGanttDays; start date: ', this.startDate, 'end date:', this.endDate)
// Layout: years => [months => [days]]
let years = {}
for (
@ -305,15 +298,13 @@ export default {
if (years[date.getFullYear() + ''] === undefined) {
years[date.getFullYear() + ''] = {}
}
if (
years[date.getFullYear() + ''][date.getMonth() + ''] ===
undefined
) {
if (years[date.getFullYear() + ''][date.getMonth() + ''] === undefined) {
years[date.getFullYear() + ''][date.getMonth() + ''] = []
}
years[date.getFullYear() + ''][date.getMonth() + ''].push(date)
this.fullWidth += this.dayWidth
}
console.debug('prepareGanttDays; years:', years)
this.$set(this, 'days', years)
},
parseTasks() {
@ -388,7 +379,7 @@ export default {
let startDate = new Date(this.startDate)
startDate.setDate(
startDate.getDate() + newRect.left / this.dayWidth
startDate.getDate() + newRect.left / this.dayWidth,
)
startDate.setUTCHours(0)
startDate.setUTCMinutes(0)
@ -397,7 +388,7 @@ export default {
this.taskDragged.startDate = startDate
let endDate = new Date(startDate)
endDate.setDate(
startDate.getDate() + newRect.width / this.dayWidth
startDate.getDate() + newRect.width / this.dayWidth,
)
this.taskDragged.startDate = startDate
this.taskDragged.endDate = endDate
@ -440,7 +431,7 @@ export default {
this.$set(
this.theTasks,
tt,
this.addGantAttributes(r)
this.addGantAttributes(r),
)
break
}
@ -488,6 +479,9 @@ export default {
this.error(e)
})
},
formatYear(date) {
return format(date, 'MMMM, yyyy')
},
},
}
</script>

View File

@ -1,4 +1,4 @@
import {parseTaskText} from '@/helpers/parseTaskText'
import {parseTaskText} from '@/modules/parseTaskText'
import TaskModel from '@/models/task'
import {formatISO} from 'date-fns'
import LabelTask from '@/models/labelTask'
@ -6,10 +6,12 @@ import LabelModel from '@/models/label'
import LabelTaskService from '@/services/labelTask'
import {mapState} from 'vuex'
import UserService from '@/services/user'
import TaskService from '@/services/task'
export default {
data() {
return {
taskService: TaskService,
labelTaskService: LabelTaskService,
userService: UserService,
}
@ -17,22 +19,35 @@ export default {
created() {
this.labelTaskService = new LabelTaskService()
this.userService = new UserService()
this.taskService = new TaskService()
},
computed: mapState({
labels: state => state.labels.labels,
}),
methods: {
createNewTask(newTaskTitle, bucketId = 0, lId = 0) {
createNewTask(newTaskTitle, bucketId = 0, lId = 0, position = 0) {
const parsedTask = parseTaskText(newTaskTitle)
const assignees = []
// Uses the following ways to get the list id of the new task:
// 1. If specified in quick add magic, look in store if it exists and use it if it does
// 2. Else check if a list was passed as parameter
// 3. Otherwise use the id from the route parameter
// 4. If none of the above worked, reject the promise with an error.
let listId = null
if (parsedTask.list !== null) {
const list = this.$store.getters['lists/findListByExactname'](parsedTask.list)
listId = list === null ? null : list.id
}
if (listId === null) {
listId = lId !== 0 ? lId : this.$route.params.listId
if (lId !== 0) {
listId = lId
}
if (typeof this.$route.params.listId !== 'undefined') {
listId = parseInt(this.$route.params.listId)
}
if (typeof listId === 'undefined' || listId === null) {
return Promise.reject('NO_LIST')
}
// Separate closure because we need to wait for the results of the user search if users were entered in the
@ -45,6 +60,7 @@ export default {
priority: parsedTask.priority,
assignees: assignees,
bucketId: bucketId,
position: position,
})
return this.taskService.create(task)
.then(task => {
@ -83,7 +99,7 @@ export default {
.then(res => {
return addLabelToTask(res)
})
.catch(e => Promise.reject(e))
.catch(e => Promise.reject(e)),
)
}
})
@ -110,7 +126,7 @@ export default {
assignees.push(user)
}
return Promise.resolve(users)
})
}),
)
})

View File

@ -1,5 +1,60 @@
import TaskCollectionService from '../../../services/taskCollection'
import {cloneDeep} from 'lodash'
import TaskCollectionService from '@/services/taskCollection'
import cloneDeep from 'lodash/cloneDeep'
import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
// FIXME: merge with DEFAULT_PARAMS in filters.vue
const DEFAULT_PARAMS = {
sort_by: ['position', 'id'],
order_by: ['asc', 'desc'],
filter_by: ['done'],
filter_value: ['false'],
filter_comparator: ['equals'],
filter_concat: 'and',
}
function createPagination(totalPages, currentPage) {
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
}
export function getRouteForPagination(page = 1, type = 'list') {
return {
name: 'list.' + type,
params: {
type: type,
},
query: {
page: page,
},
}
}
/**
* This mixin provides a base set of methods and properties to get tasks on a list.
@ -7,10 +62,9 @@ import {cloneDeep} from 'lodash'
export default {
data() {
return {
taskCollectionService: TaskCollectionService,
taskCollectionService: new TaskCollectionService(),
tasks: [],
pages: [],
currentPage: 0,
loadedList: null,
@ -19,39 +73,36 @@ export default {
searchTerm: '',
showTaskFilter: false,
params: {
sort_by: ['done', 'id'],
order_by: ['asc', 'desc'],
filter_by: ['done'],
filter_value: ['false'],
filter_comparator: ['equals'],
filter_concat: 'and',
},
params: DEFAULT_PARAMS,
}
},
watch: {
'$route.query': 'loadTasksForPage', // Only listen for query path changes
// Only listen for query path changes
'$route.query': {
handler: 'loadTasksForPage',
immediate: true,
},
'$route.path': 'loadTasksOnSavedFilter',
},
beforeMount() {
// Triggering loading the tasks in beforeMount lets the component maintain the current page, therefore the page
// is not lost after navigating back from a task detail page for example.
this.loadTasksForPage(this.$route.query)
},
created() {
this.taskCollectionService = new TaskCollectionService()
computed: {
pages() {
return createPagination(this.taskCollectionService.totalPages, this.currentPage)
},
},
methods: {
loadTasks(
page,
search = '',
params = null,
forceLoading = false,
) {
// Because this function is triggered every time on topNavigation, we're putting a condition here to only load it when we actually want to show tasks
// FIXME: This is a bit hacky -> Cleanup.
if (
this.$route.name !== 'list.list' &&
this.$route.name !== 'list.table'
this.$route.name !== 'list.table' &&
!forceLoading
) {
return
}
@ -72,52 +123,24 @@ export default {
search: search,
page: page,
}
if (JSON.stringify(currentList) === JSON.stringify(this.loadedList)) {
if (JSON.stringify(currentList) === JSON.stringify(this.loadedList) && !forceLoading) {
return
}
this.$set(this, 'tasks', [])
this.tasks = []
this.taskCollectionService.getAll(list, params, page)
.then(r => {
this.$set(this, 'tasks', r)
this.$set(this, 'pages', [])
this.tasks = r
this.currentPage = page
for (let i = 0; i < this.taskCollectionService.totalPages; i++) {
// Show ellipsis instead of all pages
if (
i > 0 && // Always at least the first page
(i + 1) < this.taskCollectionService.totalPages && // And the last page
(
// And the current with current + 1 and current - 1
(i + 1) > this.currentPage + 1 ||
(i + 1) < this.currentPage - 1
)
) {
// Only add an ellipsis if the last page isn't already one
if (this.pages[i - 1] && !this.pages[i - 1].isEllipsis) {
this.pages.push({
number: 0,
isEllipsis: true,
})
}
continue
}
this.pages.push({
number: i + 1,
isEllipsis: false,
})
}
this.loadedList = cloneDeep(currentList)
})
.catch(e => {
this.error(e)
})
},
loadTasksForPage(e) {
// The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
let page = Number(e.page)
@ -130,6 +153,11 @@ export default {
}
this.initTasks(page, search)
},
loadTasksOnSavedFilter() {
if(typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
this.loadTasks(1, '', null, true)
}
},
sortTasks() {
if (this.tasks === null || this.tasks === []) {
return
@ -140,9 +168,9 @@ export default {
if (a.done > b.done)
return 1
if (a.id > b.id)
if (a.position < b.position)
return -1
if (a.id < b.id)
if (a.position > b.position)
return 1
return 0
})
@ -168,16 +196,23 @@ export default {
this.showTaskSearch = false
}, 200)
},
getRouteForPagination(page = 1, type = 'list') {
return {
name: 'list.' + type,
params: {
type: type,
},
query: {
page: page,
},
}
saveTaskPosition(e) {
this.drag = false
const task = this.tasks[e.newIndex]
const taskBefore = this.tasks[e.newIndex - 1] ?? null
const taskAfter = this.tasks[e.newIndex + 1] ?? null
task.position = calculateItemPosition(taskBefore !== null ? taskBefore.position : null, taskAfter !== null ? taskAfter.position : null)
this.$store.dispatch('tasks/update', task)
.then(r => {
this.$set(this.tasks, e.newIndex, r)
})
.catch(e => {
this.error(e)
})
},
getRouteForPagination,
},
}

View File

@ -57,7 +57,7 @@
@click.prevent.stop="downloadAttachment(a)"
v-tooltip="$t('task.attachment.downloadTooltip')"
>
{{ $t('task.attachment.download') }}
{{ $t('misc.download') }}
</a>
<a
@click.stop="copyUrl(a)"
@ -229,7 +229,7 @@ export default {
.then((r) => {
this.$store.commit(
'attachments/removeById',
this.attachmentToDelete.id
this.attachmentToDelete.id,
)
this.success(r)
})

View File

@ -77,8 +77,8 @@
}
"
v-model="c.comment"
:has-edit-bottom="true"
:bottom-actions="actions[c.id]"
:show-save="true"
/>
</div>
</div>
@ -159,7 +159,7 @@ export default {
name: 'comments',
components: {
editor: () => ({
component: import(/* webpackChunkName: "editor" */ '../../input/editor'),
component: import('../../input/editor'),
loading: LoadingComponent,
error: ErrorComponent,
timeout: 60000,
@ -208,6 +208,9 @@ export default {
watch: {
taskId() {
this.loadComments()
this.newComment.taskId = this.taskId
this.commentEdit.taskId = this.taskId
this.commentToDelete.taskId = this.taskId
},
canWrite() {
this.makeActions()
@ -250,6 +253,7 @@ export default {
this.comments.push(r)
this.newComment.comment = ''
this.success({message: this.$t('task.comment.addedSuccess')})
this.makeActions()
})
.catch((e) => {
this.error(e)

View File

@ -23,13 +23,15 @@
@change="save"
:placeholder="$t('task.description.placeholder')"
:empty-text="$t('task.description.empty')"
v-model="task.description"/>
:show-save="true"
v-model="task.description"
/>
</div>
</template>
<script>
import LoadingComponent from '@/components/misc/loading'
import ErrorComponent from '@/components/misc/error'
import LoadingComponent from '@/components/misc/loading.vue'
import ErrorComponent from '@/components/misc/error.vue'
import {LOADING} from '@/store/mutation-types'
import {mapState} from 'vuex'
@ -38,7 +40,7 @@ export default {
name: 'description',
components: {
editor: () => ({
component: import(/* webpackChunkName: "editor" */ '@/components/input/editor'),
component: import('@/components/input/editor.vue'),
loading: LoadingComponent,
error: ErrorComponent,
timeout: 60000,
@ -78,8 +80,9 @@ export default {
this.saving = true
this.$store.dispatch('tasks/update', this.task)
.then(() => {
this.$emit('input', this.task)
.then(t => {
this.task = t
this.$emit('input', t)
this.saved = true
setTimeout(() => {
this.saved = false
@ -91,7 +94,7 @@ export default {
.finally(() => {
this.saving = false
})
}
},
},
}
</script>

View File

@ -35,7 +35,7 @@ import UserModel from '../../../models/user'
import ListUserService from '../../../services/listUsers'
import TaskAssigneeService from '../../../services/taskAssignee'
import User from '../../misc/user'
import Multiselect from '@/components/input/multiselect'
import Multiselect from '@/components/input/multiselect.vue'
export default {
name: 'editAssignees',

View File

@ -43,7 +43,7 @@ import differenceWith from 'lodash/differenceWith'
import LabelModel from '../../../models/label'
import LabelTaskService from '../../../services/labelTask'
import Multiselect from '@/components/input/multiselect'
import Multiselect from '@/components/input/multiselect.vue'
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
export default {
@ -55,7 +55,8 @@ export default {
},
taskId: {
type: Number,
required: true,
required: false,
default: () => 0,
},
disabled: {
default: false,
@ -100,9 +101,19 @@ export default {
this.query = query
},
addLabel(label, showNotification = true) {
const bubble = () => {
this.$emit('input', this.labels)
this.$emit('change', this.labels)
}
if (this.taskId === 0) {
bubble()
return
}
this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
.then(() => {
this.$emit('input', this.labels)
bubble()
if (showNotification) {
this.success({message: this.$t('task.label.addSuccess')})
}
@ -112,15 +123,24 @@ export default {
})
},
removeLabel(label) {
const removeFromState = () => {
for (const l in this.labels) {
if (this.labels[l].id === label.id) {
this.labels.splice(l, 1)
}
}
this.$emit('input', this.labels)
this.$emit('change', this.labels)
}
if (this.taskId === 0) {
removeFromState()
return
}
this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
.then(() => {
// Remove the label from the list
for (const l in this.labels) {
if (this.labels[l].id === label.id) {
this.labels.splice(l, 1)
}
}
this.$emit('input', this.labels)
removeFromState()
this.success({message: this.$t('task.label.removeSuccess')})
})
.catch(e => {
@ -128,6 +148,10 @@ export default {
})
},
createAndAddLabel(title) {
if (this.taskId === 0) {
return
}
const newLabel = new LabelModel({title: title})
this.$store.dispatch('labels/createLabel', newLabel)
.then(r => {

View File

@ -7,16 +7,17 @@
<h1
class="title input"
:class="{'disabled': !canWrite}"
@focusout="save()"
@keydown.enter.prevent.stop="save()"
@blur="save($event.target.textContent)"
@keydown.enter.prevent.stop="$event.target.blur()"
:contenteditable="canWrite ? 'true' : 'false'"
spellcheck="false"
ref="taskTitle">{{ task.title.trim() }}</h1>
<transition name="fade">
<span class="is-inline-flex is-align-items-center" v-if="loading && saving">
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span class="has-text-success is-inline-flex is-align-content-center" v-if="!loading && saved">
<span class="has-text-success is-inline-flex is-align-content-center" v-if="!loading && showSavedMessage">
<icon icon="check" class="mr-2"/>
{{ $t('misc.saved') }}
</span>
@ -25,22 +26,22 @@
</template>
<script>
import {LOADING} from '@/store/mutation-types'
import {mapState} from 'vuex'
export default {
name: 'heading',
data() {
return {
task: {title: '', identifier: '', index:''},
taskTitle: '',
saved: false,
showSavedMessage: false,
saving: false, // Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
}
},
computed: mapState({
loading: LOADING,
}),
computed: {
...mapState(['loading']),
task() {
return this.value
},
},
props: {
value: {
required: true,
@ -50,43 +51,29 @@ export default {
default: false,
},
},
watch: {
value(newVal) {
this.task = newVal
this.taskTitle = this.task.title
},
},
mounted() {
this.task = this.value
this.taskTitle = this.task.title
},
methods: {
save() {
this.$refs.taskTitle.spellcheck = false
// Pull the task title from the contenteditable
let taskTitle = this.$refs.taskTitle.textContent
this.task.title = taskTitle
// We only want to save if the title was actually change.
// Because the contenteditable does not have a change event,
// we're building it ourselves and only calling saveTask()
save(title) {
// We only want to save if the title was actually changed.
// Because the contenteditable does not have a change event
// we're building it ourselves and only continue
// if the task title changed.
if (this.task.title !== this.taskTitle) {
this.$refs.taskTitle.blur()
this.saveTask()
this.taskTitle = taskTitle
if (title === this.task.title) {
return
}
},
saveTask() {
this.saving = true
this.$store.dispatch('tasks/update', this.task)
.then(() => {
this.$emit('input', this.task)
this.saved = true
const newTask = {
...this.task,
title,
}
this.$store.dispatch('tasks/update', newTask)
.then((task) => {
this.$emit('input', task)
this.showSavedMessage = true
setTimeout(() => {
this.saved = false
this.showSavedMessage = false
}, 2000)
})
.catch(e => {
@ -95,7 +82,7 @@ export default {
.finally(() => {
this.saving = false
})
}
},
},
}
</script>

View File

@ -0,0 +1,113 @@
<template>
<div
:class="{
'is-loading': loadingInternal || loading,
'draggable': !(loadingInternal || loading),
'has-light-text': !colorIsDark(task.hexColor) && task.hexColor !== `#${task.defaultColor}` && task.hexColor !== task.defaultColor,
}"
:style="{'background-color': task.hexColor !== '#' && task.hexColor !== `#${task.defaultColor}` ? task.hexColor : false}"
@click.ctrl="() => markTaskAsDone(task)"
@click.exact="() => $router.push({ name: 'task.kanban.detail', params: { id: task.id } })"
@click.meta="() => markTaskAsDone(task)"
class="task loader-container draggable"
>
<span class="task-id">
<span class="is-done" v-if="task.done">Done</span>
<template v-if="task.identifier === ''">
#{{ task.index }}
</template>
<template v-else>
{{ task.identifier }}
</template>
</span>
<span
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
class="due-date"
v-if="task.dueDate > 0"
v-tooltip="formatDate(task.dueDate)">
<span class="icon">
<icon :icon="['far', 'calendar-alt']"/>
</span>
<span>
{{ formatDateSince(task.dueDate) }}
</span>
</span>
<h3>{{ task.title }}</h3>
<progress
class="progress is-small"
v-if="task.percentDone > 0"
:value="task.percentDone * 100" max="100">
{{ task.percentDone * 100 }}%
</progress>
<div class="footer">
<labels :labels="task.labels"/>
<priority-label :priority="task.priority"/>
<div class="assignees" v-if="task.assignees.length > 0">
<user
:avatar-size="24"
:key="task.id + 'assignee' + u.id"
:show-username="false"
:user="u"
v-for="u in task.assignees"
/>
</div>
<span class="icon" v-if="task.attachments.length > 0">
<icon icon="paperclip"/>
</span>
<span v-if="task.description" class="icon">
<icon icon="align-left"/>
</span>
</div>
</div>
</template>
<script>
import {playPop} from '../../../helpers/playPop'
import PriorityLabel from '../../../components/tasks/partials/priorityLabel'
import User from '../../../components/misc/user'
import Labels from '../../../components/tasks/partials/labels'
export default {
name: 'kanban-card',
components: {
PriorityLabel,
User,
Labels,
},
data() {
return {
loadingInternal: false,
}
},
props: {
task: {
required: true,
},
loading: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
markTaskAsDone(task) {
this.loadingInternal = true
this.$store.dispatch('tasks/update', {
...task,
done: !task.done,
})
.then(() => {
if (task.done) {
playPop()
}
})
.catch(e => {
this.error(e)
})
.finally(() => {
this.loadingInternal = false
})
},
},
}
</script>

View File

@ -1,7 +1,6 @@
<template>
<multiselect
class="control is-expanded"
v-focus
:loading="listSerivce.loading"
:placeholder="$t('list.search')"
@search="findLists"
@ -12,7 +11,7 @@
:select-placeholder="$t('list.searchSelect')"
>
<template v-slot:searchResult="props">
<span class="list-namespace-title">{{ namespace(props.option.namespaceId) }} ></span>
<span class="list-namespace-title search-result">{{ namespace(props.option.namespaceId) }} ></span>
{{ props.option.title }}
</template>
</multiselect>
@ -21,7 +20,7 @@
<script>
import ListService from '../../../services/list'
import ListModel from '../../../models/list'
import Multiselect from '@/components/input/multiselect'
import Multiselect from '@/components/input/multiselect.vue'
export default {
name: 'listSearch',
@ -32,6 +31,11 @@ export default {
foundLists: [],
}
},
props: {
value: {
required: false,
},
},
components: {
Multiselect,
},
@ -39,6 +43,14 @@ export default {
this.listSerivce = new ListService()
this.list = new ListModel()
},
watch: {
value(newVal) {
this.list = newVal
},
},
mounted() {
this.list = this.value
},
methods: {
findLists(query) {
if (query === '') {
@ -58,7 +70,9 @@ export default {
this.$set(this, 'foundLists', [])
},
select(list) {
this.list = list
this.$emit('selected', list)
this.$emit('input', list)
},
namespace(namespaceId) {
const namespace = this.$store.getters['namespaces/getNamespaceById'](namespaceId)

View File

@ -21,7 +21,7 @@
</template>
<script>
import priorites from '../../../models/priorities'
import priorites from '../../../models/constants/priorities'
export default {
name: 'priorityLabel',
@ -44,8 +44,6 @@ export default {
</script>
<style lang="scss" scoped>
@import '../../../styles/theme/variables/all';
.priority-label {
display: inline-flex;
align-items: center;

View File

@ -12,7 +12,7 @@
</template>
<script>
import priorites from '../../../models/priorities'
import priorites from '../../../models/constants/priorities'
export default {
name: 'prioritySelect',

View File

@ -122,10 +122,10 @@
import TaskService from '../../../services/task'
import TaskModel from '../../../models/task'
import TaskRelationService from '../../../services/taskRelation'
import relationKinds from '../../../models/relationKinds'
import relationKinds from '../../../models/constants/relationKinds'
import TaskRelationModel from '../../../models/taskRelation'
import Multiselect from '@/components/input/multiselect'
import Multiselect from '@/components/input/multiselect.vue'
export default {
name: 'relatedTasks',
@ -269,8 +269,6 @@ export default {
</script>
<style lang="scss">
@import '@/styles/theme/variables/all';
.add-task-relation-button {
margin-top: -3rem;

View File

@ -26,7 +26,7 @@
</template>
<script>
import datepicker from '@/components/input/datepicker'
import datepicker from '@/components/input/datepicker.vue'
export default {
name: 'reminders',

View File

@ -51,7 +51,7 @@
</template>
<script>
import repeatModes from '@/models/taskRepeatModes'
import repeatModes from '@/models/constants/taskRepeatModes'
export default {
name: 'repeatAfter',
@ -62,7 +62,7 @@ export default {
amount: 0,
type: '',
},
repeatModes: repeatModes,
repeatModes,
}
},
props: {
@ -90,6 +90,10 @@ export default {
},
methods: {
updateData() {
if (this.task.repeatMode !== repeatModes.REPEAT_MODE_DEFAULT && this.repeatAfter.amount === 0) {
return
}
this.task.repeatAfter = this.repeatAfter
this.$emit('input', this.task)
this.$emit('change')

View File

@ -135,7 +135,7 @@ export default {
showListColor: {
type: Boolean,
default: true,
}
},
},
watch: {
theTask(newVal) {
@ -178,13 +178,13 @@ export default {
this.success({
message: this.task.done ?
this.$t('task.doneSuccess') :
this.$t('task.undoneSuccess')
this.$t('task.undoneSuccess'),
}, [{
title: 'Undo',
callback: () => {
this.task.done = !this.task.done
this.markAsDone(!checked)
}
},
}])
})
.catch(e => {

View File

@ -146,8 +146,6 @@ export default {
</script>
<style lang="scss">
@import '../../styles/theme/variables/all';
.cropper {
height: 80vh;
background: transparent;

View File

@ -0,0 +1,71 @@
<template>
<card :title="$t('user.export.title')">
<p>
{{ $t('user.export.description') }}
</p>
<p>
{{ $t('user.export.descriptionPasswordRequired') }}
</p>
<div class="field">
<label class="label" for="currentPasswordDataExport">
{{ $t('user.settings.currentPassword') }}
</label>
<div class="control">
<input
class="input"
:class="{'is-danger': errPasswordRequired}"
id="currentPasswordDataExport"
:placeholder="$t('user.settings.currentPasswordPlaceholder')"
type="password"
v-model="password"
@keyup="() => errPasswordRequired = password === ''"
ref="passwordInput"
/>
</div>
<p class="help is-danger" v-if="errPasswordRequired">
{{ $t('user.deletion.passwordRequired') }}
</p>
</div>
<x-button
:loading="dataExportService.loading"
@click="requestDataExport()"
class="is-fullwidth mt-4">
{{ $t('user.export.request') }}
</x-button>
</card>
</template>
<script>
import DataExportService from '../../../services/dataExport'
export default {
name: 'data-export',
data() {
return {
dataExportService: DataExportService,
password: '',
errPasswordRequired: false,
}
},
created() {
this.dataExportService = new DataExportService()
},
methods: {
requestDataExport() {
if (this.password === '') {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
return
}
this.dataExportService.request(this.password)
.then(() => {
this.success({message: this.$t('user.export.success')})
this.password = ''
})
.catch(e => this.error(e))
},
},
}
</script>

View File

@ -0,0 +1,138 @@
<template>
<card :title="$t('user.deletion.title')" v-if="userDeletionEnabled">
<template v-if="deletionScheduledAt !== null">
<form @submit.prevent="cancelDeletion()">
<p>
{{
$t('user.deletion.scheduled', {
date: formatDateShort(deletionScheduledAt),
dateSince: formatDateSince(deletionScheduledAt),
})
}}
</p>
<p>
{{ $t('user.deletion.scheduledCancelText') }}
</p>
<div class="field">
<label class="label" for="currentPasswordAccountDelete">
{{ $t('user.settings.currentPassword') }}
</label>
<div class="control">
<input
class="input"
:class="{'is-danger': errPasswordRequired}"
id="currentPasswordAccountDelete"
:placeholder="$t('user.settings.currentPasswordPlaceholder')"
type="password"
v-model="password"
@keyup="() => errPasswordRequired = password === ''"
ref="passwordInput"
/>
</div>
<p class="help is-danger" v-if="errPasswordRequired">
{{ $t('user.deletion.passwordRequired') }}
</p>
</div>
</form>
<x-button
:loading="accountDeleteService.loading"
@click="cancelDeletion()"
class="is-fullwidth mt-4">
{{ $t('user.deletion.scheduledCancelConfirm') }}
</x-button>
</template>
<template v-else>
<form @submit.prevent="deleteAccount()">
<p>
{{ $t('user.deletion.text1') }}
</p>
<p>
{{ $t('user.deletion.text2') }}
</p>
<div class="field">
<label class="label" for="currentPasswordAccountDelete">
{{ $t('user.settings.currentPassword') }}
</label>
<div class="control">
<input
class="input"
:class="{'is-danger': errPasswordRequired}"
id="currentPasswordAccountDelete"
:placeholder="$t('user.settings.currentPasswordPlaceholder')"
type="password"
v-model="password"
@keyup="() => errPasswordRequired = password === ''"
ref="passwordInput"
/>
</div>
<p class="help is-danger" v-if="errPasswordRequired">
{{ $t('user.deletion.passwordRequired') }}
</p>
</div>
</form>
<x-button
:loading="accountDeleteService.loading"
@click="deleteAccount()"
class="is-fullwidth mt-4 is-danger">
{{ $t('user.deletion.confirm') }}
</x-button>
</template>
</card>
</template>
<script>
import AccountDeleteService from '../../../services/accountDelete'
import {mapState} from 'vuex'
import {parseDateOrNull} from '../../../helpers/parseDateOrNull'
export default {
name: 'user-settings-deletion',
data() {
return {
accountDeleteService: AccountDeleteService,
password: '',
errPasswordRequired: false,
}
},
created() {
this.accountDeleteService = new AccountDeleteService()
},
computed: mapState({
userDeletionEnabled: state => state.config.userDeletionEnabled,
deletionScheduledAt: state => parseDateOrNull(state.auth.info.deletionScheduledAt),
}),
methods: {
deleteAccount() {
if (this.password === '') {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
return
}
this.accountDeleteService.request(this.password)
.then(() => {
this.success({message: this.$t('user.deletion.requestSuccess')})
this.password = ''
})
.catch(e => this.error(e))
},
cancelDeletion() {
if (this.password === '') {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
return
}
this.accountDeleteService.cancel(this.password)
.then(() => {
this.success({message: this.$t('user.deletion.scheduledCancelSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
this.password = ''
})
.catch(e => this.error(e))
},
},
}
</script>

View File

@ -1,18 +0,0 @@
export const applyDrag = (arr, dragResult) => {
const {removedIndex, addedIndex, payload} = dragResult
if (removedIndex === null && addedIndex === null) return arr
const result = [...arr]
// The payload comes from the task itself
let itemToAdd = payload
if (removedIndex !== null) {
itemToAdd = result.splice(removedIndex, 1)[0]
}
if (addedIndex !== null) {
result.splice(addedIndex, 0, itemToAdd)
}
return result
}

61
src/helpers/auth.ts Normal file
View File

@ -0,0 +1,61 @@
import {HTTPFactory} from '@/http-common'
import {AxiosResponse} from 'axios'
let savedToken: string | null = null
/**
* Saves a token while optionally saving it to lacal storage. This is used when viewing a link share:
* It enables viewing multiple link shares indipendently from each in multiple tabs other without overriding any other open ones.
* @param token
* @param persist
*/
export const saveToken = (token: string, persist: boolean) => {
savedToken = token
if (persist) {
localStorage.setItem('token', token)
}
}
/**
* Returns a saved token. If there is one saved in memory it will use that before anything else.
* @returns {string|null}
*/
export const getToken = (): string | null => {
if (savedToken !== null) {
return savedToken
}
savedToken = localStorage.getItem('token')
return savedToken
}
/**
* Removes all tokens everywhere.
*/
export const removeToken = () => {
savedToken = null
localStorage.removeItem('token')
}
/**
* Refreshes an auth token while ensuring it is updated everywhere.
* @returns {Promise<AxiosResponse<any>>}
*/
export const refreshToken = (persist: boolean): Promise<AxiosResponse> => {
const HTTP = HTTPFactory()
return HTTP.post('user/token', null, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
.then(r => {
saveToken(r.data.token, persist)
return Promise.resolve(r)
})
.catch(e => {
// eslint-disable-next-line
console.log('Error renewing token: ', e)
return Promise.reject(e)
})
}

View File

@ -0,0 +1,19 @@
export const calculateItemPosition = (positionBefore: number | null, positionAfter: number | null): number => {
if (positionBefore === null && positionAfter === null) {
return 0
}
// If there is no task before, our task is the first task in which case we let it have half of the position of the task after it
if (positionBefore === null && positionAfter !== null) {
return positionAfter / 2
}
// If there is no task after it, we just add 2^16 to the last position to have enough room in the future
if (positionBefore !== null && positionAfter === null) {
return positionBefore + Math.pow(2, 16)
}
// If we have both a task before and after it, we acually calculate the position
// @ts-ignore - can never be null but TS does not seem to understand that
return positionBefore + (positionAfter - positionBefore) / 2
}

View File

@ -0,0 +1,18 @@
import {calculateItemPosition} from './calculateItemPosition'
it('should calculate the task position', () => {
const result = calculateItemPosition(10, 100)
expect(result).toBe(55)
})
it('should return 0 if no position was provided', () => {
const result = calculateItemPosition(null, null)
expect(result).toBe(0)
})
it('should calculate the task position for the first task', () => {
const result = calculateItemPosition(null, 100)
expect(result).toBe(50)
})
it('should calculate the task position for the last task', () => {
const result = calculateItemPosition(10, null)
expect(result).toBe(65546)
})

View File

@ -0,0 +1,7 @@
export const downloadBlob = (url: string, filename: string) => {
const link = document.createElement('a')
link.href = url
link.setAttribute('download', filename)
link.click()
window.URL.revokeObjectURL(url)
}

3
src/helpers/find.ts Normal file
View File

@ -0,0 +1,3 @@
export function findIndexById(array : [], id : string | number) {
return array.findIndex(({id: currentId}) => currentId === id)
}

View File

@ -1,6 +1,6 @@
export const getListTitle = (l, $t) => {
if (l.id === -1) {
return $t('list.pseudo.favorites.title');
return $t('list.pseudo.favorites.title')
}
return l.title;
return l.title
}

View File

@ -1,12 +1,12 @@
export const getNamespaceTitle = (n, $t) => {
if (n.id === -1) {
return $t('namespace.pseudo.sharedLists.title');
return $t('namespace.pseudo.sharedLists.title')
}
if (n.id === -2) {
return $t('namespace.pseudo.favorites.title');
return $t('namespace.pseudo.favorites.title')
}
if (n.id === -3) {
return $t('namespace.pseudo.savedFilters.title');
return $t('namespace.pseudo.savedFilters.title')
}
return n.title;
return n.title
}

38
src/helpers/migrator.ts Normal file
View File

@ -0,0 +1,38 @@
export interface Migrator {
name: string
identifier: string
isFileMigrator?: boolean
}
export const getMigratorFromSlug = (slug: string): Migrator => {
switch (slug) {
case 'wunderlist':
return {
name: 'Wunderlist',
identifier: 'wunderlist',
}
case 'todoist':
return {
name: 'Todoist',
identifier: 'todoist',
}
case 'trello':
return {
name: 'Trello',
identifier: 'trello',
}
case 'microsoft-todo':
return {
name: 'Microsoft Todo',
identifier: 'microsoft-todo',
}
case 'vikunja-file':
return {
name: 'Vikunja Export',
identifier: 'vikunja-file',
isFileMigrator: true,
}
default:
throw Error('Unknown migrator slug ' + slug)
}
}

View File

@ -0,0 +1,13 @@
interface Provider {
name: string
key: string
authUrl: string
clientId: string
}
export const redirectToProvider = (provider: Provider, redirectUrl: string) => {
const state = Math.random().toString(36).substring(2, 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}`
}

Some files were not shown because too many files have changed in this diff Show More