Compare commits

...

1523 Commits

Author SHA1 Message Date
2d2fcc5d96 adapt checks in team views to check whether team.oidcId is present and not empty 2024-02-23 11:16:21 +01:00
0b8d8c09c8 make sso teams non editable by neither admin nor team members (except for leaving team) 2024-02-23 11:14:55 +01:00
75d6387e20 mark teams from sso in ListTeams 2024-02-23 11:13:02 +01:00
070b40076d extend teams model to include oidcId 2024-02-23 11:10:08 +01:00
77f0776d0b allow custom_scope to be demanded by vikunja, add scope to IProvider 2024-02-23 11:10:08 +01:00
652d3c7384
chore: add archival note 2024-02-07 15:03:24 +01:00
447641c222 chore: apply lint fixes 2024-02-07 12:23:09 +00:00
362be53a47 feat: use recommended vue-linting 2024-02-07 12:23:09 +00:00
46eabdfe6b fix(deps): update sentry-javascript monorepo to v7.100.1 2024-02-07 11:53:00 +00:00
a0c5a464a5
feat(progress): less rounding 2024-02-07 11:36:57 +01:00
e78ab476fc
chore(progress): cleanup unused css 2024-02-07 11:35:32 +01:00
aebb047d18
fix(progress): move customizations into progress bar component 2024-02-07 11:24:20 +01:00
7bb110b20e
feat: add ProgressBar component 2024-02-07 11:12:21 +01:00
f148a43390 fix(deps): update dependency ufo to v1.4.0 2024-02-07 10:08:12 +00:00
aac70d3823 fix(deps): update dependency @kyvg/vue3-notification to v3.1.4 2024-02-07 09:20:36 +00:00
21126793ab
fix(test): make test assertion work again 2024-02-06 23:13:38 +01:00
b057fb2784
fix(reminders): set reminder date on datepicker when editing a reminder
Setting an actual reminder date (not a relative one) flowed only from the component to the outside when setting it. When editing it, the reminder date would not be populated, causing the datepicker date to stay at the current date.
2024-02-06 18:46:15 +01:00
58c7da019d
fix(notifications): mark all notifications as read in ui directly when marking as read on the server
This caused the notifications to stay on "unread" when marking them as read, making an unpleasant user experience
2024-02-06 18:34:42 +01:00
70f48eaaca
fix(task): make sure the drag handle is shown as intended
Due to a previous refactoring, the drag handle was always shown instead of only on hover. The css class was moved out of the task component, but its styles weren't

Related to vikunja/frontend#3934
2024-02-06 18:29:17 +01:00
6cc75928d8
fix(task): remove default task color
Previously, the task would use the default color. This was now removed, as this resulted in the default color not being visible on tasks.

Resolves https://github.com/go-vikunja/frontend/issues/135#issuecomment-1917576392
2024-02-06 18:18:44 +01:00
dc360d4a18
chore(editor): don't set editor content intitially 2024-02-06 18:03:27 +01:00
45ca0602f5
feat(editor): use primary color for currently selected node 2024-02-06 16:09:38 +01:00
9d39ccf15c
fix(assignees): use correct amount of spacing in assignee selection 2024-02-06 15:44:39 +01:00
28e83325d7
fix(kanban): assignee spacing 2024-02-06 15:39:05 +01:00
aff48ddd9d
fix(kanban): bottom spacing of labels 2024-02-06 15:34:22 +01:00
5b2a9a42c0
fix(gantt): correctly import languages from dayjs
Resolves https://community.vikunja.io/t/error-in-gannt-with-spanish-language/1973/3
2024-02-05 21:57:21 +01:00
45f5d522d1
docs: update readme
Copied from https://github.com/go-vikunja/frontend/pull/146
2024-02-05 21:16:30 +01:00
4f27e4a477 fix(deps): update tiptap to v2.2.1 2024-02-05 10:37:28 +00:00
d0dc86fd58 fix(deps): update dependency vue-i18n to v9.9.1 2024-01-31 02:20:29 +00:00
0484923b8a fix(deps): update sentry-javascript monorepo to v7.99.0 2024-01-30 17:18:57 +00:00
5f2fb01e90 fix(deps): update dependency floating-vue to v5.2.2 2024-01-30 14:19:37 +00:00
bd18524f36 chore(deps): update dev-dependencies 2024-01-30 00:19:43 +00:00
7375a87f2f fix(deps): update dependency @fortawesome/vue-fontawesome to v3.0.6 2024-01-29 21:18:09 +00:00
ccff276397 chore(deps): update pnpm to v8.15.1 2024-01-29 20:19:25 +00:00
30b21fc11c fix(deps): update dependency floating-vue to v5.2.1 2024-01-29 14:22:11 +00:00
7c98ddc20b fix(deps): update tiptap to v2.2.0 2024-01-29 13:21:38 +00:00
6ba02a0f10 chore(deps): update pnpm to v8.15.0 2024-01-29 07:32:55 +00:00
676d2b6215 chore(deps): update dependency @types/node to v20.11.10 2024-01-29 07:32:26 +00:00
Frederick [Bot]
85e612451f chore(i18n): update translations via Crowdin 2024-01-29 00:25:58 +00:00
d411de99f1
chore: release preparation 2024-01-28 17:43:53 +01:00
228d652b03
fix(kanban): make sure spacing between assignees and other task details works out evenly 2024-01-28 16:41:24 +01:00
b3e2107503
fix(task): don't show assignee edit buttons and input when the user does not have the permission to edit 2024-01-28 13:30:29 +01:00
a579a8e65f
fix(task): don't show edit button when the user does not have permission to edit the task 2024-01-28 13:24:58 +01:00
ee980e2a00
fix(openid): use the calculated redirect url when authenticating with openid providers
Resolves https://github.com/go-vikunja/desktop/issues/12
2024-01-28 12:42:45 +01:00
394dbe0055 chore(deps): update dev-dependencies 2024-01-28 04:19:06 +00:00
30d599369f chore(deps): update dev-dependencies 2024-01-27 00:18:54 +00:00
631b02d2ee
chore: only show webhooks overview table when there are webhooks 2024-01-27 00:01:11 +01:00
326bfb557a
chore: only show webhooks overview table when there are webhooks 2024-01-27 00:00:31 +01:00
cd0149ef69
fix(kanban): make sure the checklist summary uses the correct text color
Related-To https://github.com/go-vikunja/frontend/issues/135
2024-01-26 21:44:20 +01:00
78d4a518a3
fix(tasks): don't load tasks multiple times when viewing list or gantt view 2024-01-26 21:33:20 +01:00
3c1041902e
fix(table view): make sure popup does not overlap 2024-01-26 21:22:51 +01:00
e3cae0ed7f
fix(filter): validate filter title field after loading a filter for edit
Related to vikunja/frontend#3866
2024-01-26 11:29:46 +01:00
fc8bd6a9ca chore(deps): update dev-dependencies 2024-01-26 05:19:03 +00:00
5a6e5619e3 fix(deps): update dependency axios to v1.6.7 2024-01-25 20:20:22 +00:00
9c9f806e62 fix(deps): update sentry-javascript monorepo to v7.98.0 2024-01-25 13:55:42 +00:00
67216579bc
fix(auth): correctly construct redirect url from current window href 2024-01-25 14:24:30 +01:00
a8df935ddb fix(deps): update sentry-javascript monorepo to v7.97.0 2024-01-25 11:20:20 +00:00
bb4746f226 chore(deps): update dev-dependencies 2024-01-25 07:50:16 +00:00
31590236aa fix(deps): update dependency axios to v1.6.6 2024-01-24 23:19:33 +00:00
00d48a6178 chore(deps): update dev-dependencies 2024-01-24 06:19:41 +00:00
5169cca8d8 fix(deps): update sentry-javascript monorepo to v7.95.0 2024-01-23 19:18:41 +00:00
255a7d565c chore(deps): update pnpm to v8.14.3 2024-01-23 10:20:44 +00:00
8dbaee5dfb chore(deps): update dev-dependencies to v6.19.1 2024-01-23 00:19:30 +00:00
69b0b19482 fix(deps): update dependency date-fns to v3.3.1 2024-01-22 10:44:49 +00:00
eae89d37f1 chore(deps): update pnpm to v8.14.2 2024-01-22 10:19:22 +00:00
7d19859816 chore(deps): update dev-dependencies 2024-01-22 00:18:43 +00:00
c7b70844c6
fix(color picker): when picking a color, the color picker should not be black afterwards 2024-01-21 20:25:19 +01:00
b8c21c2ade
fix(labels): text and background combination in dark mode 2024-01-21 20:20:00 +01:00
57c99a22a0
fix(editor): use manual input prompt instead of window.prompt
Resolves vikunja/desktop#184
2024-01-21 20:08:10 +01:00
8ea97f3ffc
fix(editor): use a stable image id to prevent constant re-rendering 2024-01-21 15:45:18 +01:00
0b3604d167
fix(editor): render images without crashing 2024-01-21 15:00:26 +01:00
c5ba7fcb73
fix(editor): focus the editor when clicking on the whole edit container 2024-01-21 13:52:13 +01:00
5a25685d53
fix(editor): don't bubble up changes when no changes were made
Related https://community.vikunja.io/t/saving-an-empty-description-in-kanban-view-break-ui/1914/3
2024-01-21 13:44:40 +01:00
da311fce9e
fix(kanban): ensure text and icon color only depends on the card background, not on the color scheme
Related https://github.com/go-vikunja/frontend/issues/135#issuecomment-1900701258
2024-01-21 00:10:05 +01:00
0fdf1ca027
fix(notifications): read indicator size 2024-01-21 00:01:04 +01:00
f8e907a8c1
fix(notifications): always left-align notification text 2024-01-20 23:59:57 +01:00
af7ca8ad8f
fix(project): always use the appropriate color for task estimate during deletion dialoge 2024-01-20 23:54:03 +01:00
nor
92f7d9ded5 feat: datepicker locale support (#3878)
Reviewed-on: vikunja/frontend#3878
Reviewed-by: konrad <k@knt.li>
Co-authored-by: nor <zorodey@outlook.com>
Co-committed-by: nor <zorodey@outlook.com>
2024-01-20 18:50:00 +00:00
41ccaea78b fix(deps): update dependency date-fns to v3.3.0 2024-01-20 06:19:03 +00:00
c5696f3e2a chore(deps): update dependency vite to v5.0.12 2024-01-20 00:19:28 +00:00
898707664c fix(deps): update sentry-javascript monorepo to v7.94.1 2024-01-19 13:19:38 +00:00
d0b5bef68a chore(deps): update dependency happy-dom to v13.2.0 2024-01-19 00:20:24 +00:00
e395d4efdb fix(deps): update dependency vue to v3.4.15 2024-01-18 14:19:39 +00:00
ce54132868 chore(deps): update dev-dependencies 2024-01-18 07:18:54 +00:00
07d4d1e537 fix(deps): update dependency floating-vue to v5.2.0 2024-01-17 13:19:31 +00:00
a701b0452e fix(deps): update dependency floating-vue to v5.1.1 2024-01-17 12:18:43 +00:00
af65efcd27 chore(deps): update dev-dependencies (major) (#3890)
Reviewed-on: vikunja/frontend#3890
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-17 09:17:35 +00:00
dc2afb9e8d chore(deps): update dev-dependencies 2024-01-17 07:18:31 +00:00
e123d4f825 chore(perf): import some modules dynamically (#3179)
Reviewed-on: vikunja/frontend#3179
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2024-01-16 14:24:24 +00:00
b72c963256 fix(deps): update dependency vue to v3.4.14 2024-01-16 12:10:13 +00:00
149bbf17eb fix(deps): update dependency floating-vue to v5.1.0 2024-01-16 12:01:54 +00:00
265d60cf42 fix(deps): update vueuse to v10.7.2 2024-01-16 12:01:44 +00:00
23c9f51e73 fix(deps): update dependency sortablejs to v1.15.2 2024-01-16 12:01:11 +00:00
ff697d0c7a chore(deps): update dev-dependencies 2024-01-16 11:50:52 +00:00
00588cf59f chore(deps): pin node.js (#3895)
Reviewed-on: vikunja/frontend#3895
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-16 11:49:00 +00:00
01089f4f3d fix(deps): update tiptap to v2.1.16 (#3892)
Reviewed-on: vikunja/frontend#3892
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-16 11:17:28 +00:00
a7461d1ddd
chore(deps): increase renovate timeout 2024-01-16 12:15:04 +01:00
a451189bb6
fix(test): make date assertion not brittle 2024-01-16 10:36:29 +01:00
bf9af27fc3
fix(task): update due date when marking a task done 2024-01-15 23:33:02 +01:00
5619fda0f2
fix(task): bubble date changes from the picker up
Resolves https://github.com/go-vikunja/frontend/issues/142
2024-01-15 23:23:57 +01:00
167953b26b
fix(editor): use higher-contrast colors for links and code 2024-01-15 22:11:24 +01:00
664bf0a5f4
fix(tasks): make sure tasks show up if their parent task is not available in the current view
Related https://github.com/go-vikunja/frontend/issues/136
Related https://community.vikunja.io/t/subtasks-are-hidden-when-parent-tasks-are-moved/1911
2024-01-15 21:46:47 +01:00
5e991f3024
fix: lint 2024-01-15 16:21:00 +01:00
28050d9cd5
fix(labels): make color reset work 2024-01-15 14:00:08 +01:00
e94b71d577
fix(editor): list icons 2024-01-15 13:39:17 +01:00
336ce217d3 chore(deps): update node.js to v20.11 (#3888)
Reviewed-on: vikunja/frontend#3888
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-11 11:30:14 +00:00
Frederick [Bot]
ce01085951 chore(i18n): update translations via Crowdin 2024-01-11 00:11:57 +00:00
96a6d43a3f
fix(quick add magic): ensure month is removed from task text
Resolves vikunja/frontend#3874
2024-01-10 23:54:42 +01:00
13d63e34aa
fix(task): don't immediately re-trigger date change when nothing changed
Resolves https://community.vikunja.io/t/reminder-duplication/76/21?u=kolaente
2024-01-10 23:27:14 +01:00
a8441c72b8 fix(deps): update dependency vue to v3.4.8 (#3886)
Reviewed-on: vikunja/frontend#3886
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 20:16:50 +00:00
230fa6ce66 fix(deps): update dependency floating-vue to v5 (#3887)
Reviewed-on: vikunja/frontend#3887
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 18:18:53 +00:00
069c491fbd fix(deps): update sentry-javascript monorepo to v7.93.0 (#3859)
Reviewed-on: vikunja/frontend#3859
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 16:01:02 +00:00
a9eae95d67 chore(deps): update pnpm to v8.14.1 (#3885)
Reviewed-on: vikunja/frontend#3885
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 15:57:10 +00:00
50502d9d11 fix(deps): update vueuse to v10.7.1 (#3872)
Reviewed-on: vikunja/frontend#3872
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 15:04:27 +00:00
18af6edc82 fix(deps): update tiptap to v2.1.15 (#3884)
Reviewed-on: vikunja/frontend#3884
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:49:03 +00:00
d048b61eb3 fix(deps): update dependency floating-vue to v2.0.0 (#3883)
Reviewed-on: vikunja/frontend#3883
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:36:14 +00:00
996607e670 fix(deps): update dependency dompurify to v3.0.8 (#3881)
Reviewed-on: vikunja/frontend#3881
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:35:50 +00:00
e33ebe1831 fix(deps): update dependency vue-i18n to v9.9.0 (#3880)
Reviewed-on: vikunja/frontend#3880
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:27:02 +00:00
557b0ffec7 chore(deps): update dependency node to v20.11.0 2024-01-10 12:04:17 +00:00
dae6cdb9d7 fix(deps): update dependency @kyvg/vue3-notification to v3.1.3 (#3864)
Reviewed-on: vikunja/frontend#3864
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:59:31 +00:00
158e4d690f chore(deps): update dev-dependencies (#3861)
Reviewed-on: vikunja/frontend#3861
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:51:20 +00:00
691eb84a99 fix(deps): update dependency date-fns to v3 (#3857)
Reviewed-on: vikunja/frontend#3857
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:29:48 +00:00
698ee7e163 fix(deps): update dependency axios to v1.6.5 (#3871)
Reviewed-on: vikunja/frontend#3871
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:28:28 +00:00
ce822573df fix(deps): update dependency vue to v3.4.7 (#3873)
Reviewed-on: vikunja/frontend#3873
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:19:30 +00:00
198abee01d chore(deps): update pnpm to v8.14.0 2024-01-10 10:59:39 +00:00
Frederick [Bot]
e5bea087be chore(i18n): update translations via Crowdin 2024-01-09 00:10:45 +00:00
Frederick [Bot]
4956fbb669 chore(i18n): update translations via Crowdin 2024-01-08 11:50:29 +00:00
0351148288
fix(ci): use working crowdin image 2024-01-07 20:20:14 +01:00
654806211e
fix(ci): use working image for crowdin update step 2024-01-04 13:22:27 +01:00
09572dbe61
fix(ci): use working crowdin image 2023-12-27 15:44:36 +01:00
fae5b764dd
fix(notifications): unread indicator spacing 2023-12-23 15:53:17 +01:00
7f70471894
feat(reminders): show reminders in notifications bar 2023-12-23 15:48:29 +01:00
e98e5a0d2f
fix(openid): use the full path when building the redirect url, not only the host
Resolves vikunja/api#1661
2023-12-20 13:23:56 +01:00
21e34d6d54 fix(deps): update dependency @intlify/unplugin-vue-i18n to v2 (#3862)
Reviewed-on: vikunja/frontend#3862
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-20 11:22:56 +00:00
4b71604513
chore: release preparation 2023-12-19 16:17:19 +01:00
609b86e614
chore(deps): update lockfile 2023-12-19 15:07:45 +01:00
4faffd37a7
fix(deps): update dependency @intlify/unplugin-vue-i18n to v1.6.0
(cherry picked from commit 1138285fd9ea3932d9b5dfe181d8db5d5d7f6690)
2023-12-19 15:07:29 +01:00
008585d61f
fix(deps): update dependency @github/hotkey to v3
(cherry picked from commit c92f0c849169b21d312563bf06b7a42d5e705d4e)
2023-12-19 15:07:29 +01:00
d0ae285663
fix(deps): update dependency @kyvg/vue3-notification to v3.1.2
(cherry picked from commit 15b4c6062cd4c950bdafdb8da16aff44db5e3a5c)
2023-12-19 15:07:29 +01:00
00d0f88798
fix(deps): update dependency vue to v3.3.13
(cherry picked from commit 2c784ecd656594f82e3ef238044f932c7edc6b75)
2023-12-19 15:07:29 +01:00
4cf31080f8
fix(deps): update sentry-javascript monorepo to v7.88.0
(cherry picked from commit 12caae90eeb78ae41f2c22f7673250d06e4ae4d4)
2023-12-19 15:07:25 +01:00
21e54de3b8 chore(deps): update dev-dependencies (#3856)
Reviewed-on: vikunja/frontend#3856
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-19 13:48:35 +00:00
143cabbac5 chore(deps): update pnpm to v8.12.1 2023-12-19 13:15:53 +00:00
0607c97da9
feat(api tokens): allow selecting all permissions 2023-12-17 18:39:54 +01:00
be925b29e3
fix(api tokens): make deletion of old tokens work 2023-12-17 17:47:12 +01:00
2541733c71
fix(tasks): prevent endless references
This would lead to failing attempts when updating the task later on (for example marking it as favorite)
2023-12-13 19:27:35 +01:00
4d6fd9ecc4
fix(tasks): favorited sub tasks are not shown in favorites pseudo list 2023-12-13 19:22:28 +01:00
818f31c220
fix(tasks): update sub task relations in list view after they were created
Resolves vikunja/frontend#3853
2023-12-13 19:15:48 +01:00
34e4862c88
fix(kanban): make sure kanban cards always have text color matching their background
Resolves https://github.com/go-vikunja/frontend/issues/135
2023-12-13 18:54:48 +01:00
0d074113f1 chore(deps): update dev-dependencies (#3846)
Reviewed-on: vikunja/frontend#3846
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-13 17:45:31 +00:00
0730955403
fix(sw): remove debug option via env as it would not be replaced correctly in prod builds 2023-12-13 18:37:56 +01:00
75035ec1f8
fix(navigation): show filter settings dropdown
Resolves vikunja/frontend#3851
2023-12-13 18:30:10 +01:00
923fc4eaa0 fix(editor): keep editor open when emptying content from the outside (#3852)
Reviewed-on: vikunja/frontend#3852
2023-12-11 23:13:30 +00:00
ea7dab68ae
fix(editor): add workaround for checklist tiptap bug 2023-12-11 23:58:46 +01:00
8b9e5e54af
fix(test): use correct file input 2023-12-11 23:23:25 +01:00
a4a2b95dc7
chore: cleanup 2023-12-11 22:37:28 +01:00
9fdb6a8d24
feat(task): add more tests 2023-12-11 22:35:32 +01:00
fc6b707405
fix(task): use empty description helper everywhere 2023-12-11 22:35:09 +01:00
9efe860f26
fix(editor): keep editor open when emptying content from the outside 2023-12-11 21:58:39 +01:00
af13d68c48
fix(editor): show editor if there is no content initially 2023-12-11 21:55:47 +01:00
3cb1e7dede
chore: debug 2023-12-11 21:01:38 +01:00
e27d88785e fix(deps): update dependency vue to v3.3.10 (#3843)
Reviewed-on: vikunja/frontend#3843
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 13:01:56 +00:00
b1fc5dbd97 chore(deps): update dev-dependencies (major) (#3827)
Reviewed-on: vikunja/frontend#3827
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 12:42:11 +00:00
1786dee042 fix(deps): update dependency @github/hotkey to v2.3.1 (#3845)
Reviewed-on: vikunja/frontend#3845
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 12:19:03 +00:00
8491caf419 fix(deps): update vueuse to v10.7.0 (#3844)
Reviewed-on: vikunja/frontend#3844
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 12:16:48 +00:00
3b15293b47 chore(deps): update dev-dependencies (#3842)
Reviewed-on: vikunja/frontend#3842
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 12:07:43 +00:00
e65f13eaa3 fix(deps): update dependency sortablejs to v1.15.1 (#3841)
Reviewed-on: vikunja/frontend#3841
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 12:04:24 +00:00
25561f3229 fix(deps): update dependency vue-i18n to v9.8.0 (#3833)
Reviewed-on: vikunja/frontend#3833
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 12:02:39 +00:00
97d149c2f5 fix(deps): update sentry-javascript monorepo to v7.85.0 (#3831)
Reviewed-on: vikunja/frontend#3831
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 11:59:40 +00:00
c8809d899a
fix(kanban): check if doneBucketId is set 2023-12-01 15:10:48 +01:00
203041ae36 fix(deps): update dependency vue to v3.3.9 (#3837)
Reviewed-on: vikunja/frontend#3837
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-01 11:36:42 +00:00
11d11012e7 chore(deps): update dependency node (#3834)
Reviewed-on: vikunja/frontend#3834
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-01 11:07:14 +00:00
30046c7ff5 chore(deps): update dev-dependencies (#3835)
Reviewed-on: vikunja/frontend#3835
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-01 10:53:44 +00:00
668ff753b3 fix(deps): update font awesome to v6.5.1 (#3839)
Reviewed-on: vikunja/frontend#3839
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-01 10:50:30 +00:00
0b68ab93e1 fix(deps): update tiptap to v2.1.13 (#3840)
Reviewed-on: vikunja/frontend#3840
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-01 10:50:22 +00:00
af22d2e88a chore(deps): update pnpm to v8.11.0 2023-12-01 10:31:40 +00:00
611e9feb6d
chore(deps): update sub-dependencies 2023-11-28 22:50:15 +01:00
e770496524
fix(editor): don't check parent checkbox when child label was clicked 2023-11-27 13:11:24 +01:00
1cbb93ea9b
fix(tasks): make sure tasks are fully clickable
Resolves vikunja/frontend#3838
2023-11-27 12:47:26 +01:00
Frederick [Bot]
f7c06e53b7 [skip ci] Updated translations via Crowdin 2023-11-27 00:25:06 +00:00
Frederick [Bot]
240906f236 [skip ci] Updated translations via Crowdin 2023-11-24 00:24:04 +00:00
282ec3164b chore(deps): update dev-dependencies (#3829)
Reviewed-on: vikunja/frontend#3829
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-22 10:20:23 +00:00
Frederick [Bot]
a994264234 [skip ci] Updated translations via Crowdin 2023-11-22 00:25:02 +00:00
4868ac824e
feat(i18n): add Slovene language for selection in the ui 2023-11-21 22:14:15 +01:00
0c58ea1ade
fix(editor): don't crash when the component isn't completely mounted 2023-11-21 13:25:55 +01:00
f45303c2e3
fix(editor): image paste handling 2023-11-21 13:23:05 +01:00
c3e53970de
chore(deps): update lockfile 2023-11-21 13:03:06 +01:00
Frederick [Bot]
0795c0e448 [skip ci] Updated translations via Crowdin 2023-11-21 00:24:01 +00:00
cfd46dc39b fix(deps): update vueuse to v10.6.1 (#3822)
Reviewed-on: vikunja/frontend#3822
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-20 20:40:34 +00:00
debae2326e
fix(editor): don't create empty "blob" files when pasting images 2023-11-20 12:35:19 +01:00
23d670525d chore(deps): update dessant/repo-lockdown action to v4 2023-11-20 05:13:05 +00:00
2967019cd9
feat(editor): mark a checkbox item as done when clicking on its text 2023-11-18 18:01:09 +01:00
d3497c96d7
fix(editor): correctly resolve images in descriptions
Resolves vikunja/frontend#3808
2023-11-18 17:17:14 +01:00
bd83294ac0
fix(editor): alignment and focus states 2023-11-18 17:03:47 +01:00
6c4f1e1cbf
fix(editor): make initial editor mode (preview/edit) work 2023-11-18 16:54:29 +01:00
fa269f155a
chore(filter): remove debug log 2023-11-18 16:44:51 +01:00
602d15985b
fix(filter): don't immediately re-trigger prepareFilter 2023-11-18 16:40:20 +01:00
cc3c1a9429 chore(deps): update dev-dependencies (#3828)
Reviewed-on: vikunja/frontend#3828
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-18 14:21:37 +00:00
cfd49864e1 fix(deps): update dependency axios to v1.6.2 (#3820)
Reviewed-on: vikunja/frontend#3820
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-18 14:21:02 +00:00
6711a08de9 fix(deps): update sentry-javascript monorepo to v7.80.1 (#3819)
Reviewed-on: vikunja/frontend#3819
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 22:50:00 +00:00
7fe33c6662 fix(deps): update dependency @types/sortablejs to v1.15.5 (#3818)
Reviewed-on: vikunja/frontend#3818
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 22:49:51 +00:00
e61b215dc1 fix(deps): update dependency ufo to v1.3.2 (#3824)
Reviewed-on: vikunja/frontend#3824
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 22:03:25 +00:00
3b5cb1ade3 fix(deps): update dependency vue-i18n to v9.7.0 (#3825)
Reviewed-on: vikunja/frontend#3825
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 22:01:43 +00:00
89e28cbdf2 chore(deps): update dev-dependencies (#3826)
Reviewed-on: vikunja/frontend#3826
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 21:59:59 +00:00
e9e836f068 chore(deps): update pnpm to v8.10.5 2023-11-17 21:40:42 +00:00
aa5e11915e
fix(filter): don't prevent entering date math strings
Resolves https://community.vikunja.io/t/filter-setting-s/1791/2
2023-11-17 19:38:55 +01:00
7f279c98e1
fix(tasks): don't use the filter for upcoming when one is set for the home page
Resolves https://github.com/go-vikunja/frontend/issues/132
2023-11-17 19:08:08 +01:00
3c1861eb6a
fix(settings): move overdue remindeer time below 2023-11-17 19:03:58 +01:00
75262b716f
fix(kanban): opening a task from the kanban board and then reloading the page should not crash everything when then navigating back
Before this fix, the following would not work:

1. Open the kanban view of a project
2. Click on a task to open it in a modal
3. Reload the page
4. Using your browser's back button, navigate back

Instead of showing the kanban board with the task modal closed, it would
navigate to `/projects/0/kanban` and crash.
2023-11-15 23:43:39 +01:00
7e623d919e fix(filters): infinite loop when creating filters with dates (#3061)
Rather than putting in a truncated version of the date/time with `startDate.getDate`, use the iso formatted version which includes the timezone data. I have no idea if this has ramifications elsewhere in the app, but it solves the problems I was seeing.

Co-authored-by: Sean Hurley <sean.hurley6@gmail.com>
Reviewed-on: vikunja/frontend#3061
Reviewed-by: konrad <k@knt.li>
Co-authored-by: ThatHurleyGuy <sean@hurley.io>
Co-committed-by: ThatHurleyGuy <sean@hurley.io>
2023-11-15 12:10:18 +00:00
3f42ce2b34
fix(filter): make other filters are not available for project selection 2023-11-15 12:47:19 +01:00
8b8da40265 chore(deps): update dev-dependencies (#3821)
Reviewed-on: vikunja/frontend#3821
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-15 11:23:57 +00:00
Frederick [Bot]
0f23cc2162 [skip ci] Updated translations via Crowdin 2023-11-14 00:13:32 +00:00
Frederick [Bot]
adf80d9184 [skip ci] Updated translations via Crowdin 2023-11-11 00:13:51 +00:00
Frederick [Bot]
e3dfcafc29 [skip ci] Updated translations via Crowdin 2023-11-10 00:13:52 +00:00
Frederick [Bot]
a9df58109f [skip ci] Updated translations via Crowdin 2023-11-09 00:14:14 +00:00
59a7360608
feat(migration): proper wording for async migration 2023-11-09 00:14:37 +01:00
29e128c64c chore(deps): update dev-dependencies (#3813)
Reviewed-on: vikunja/frontend#3813
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 19:24:57 +00:00
cec50d912c fix(deps): update dependency vue to v3.3.8 (#3814)
Reviewed-on: vikunja/frontend#3814
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 19:24:39 +00:00
53564ec46c fix(deps): update dependency @types/lodash.clonedeep to v4.5.9 (#3817)
Reviewed-on: vikunja/frontend#3817
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 17:07:48 +00:00
e9cd7aac69 fix(deps): update dependency @intlify/unplugin-vue-i18n to v1.5.0 (#3812)
Reviewed-on: vikunja/frontend#3812
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 17:06:56 +00:00
a47bfb3ff1 fix(deps): update dependency @types/is-touch-device to v1.0.2 (#3816)
Reviewed-on: vikunja/frontend#3816
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 15:06:23 +00:00
86eb4da2e3 fix(deps): update dependency @fortawesome/vue-fontawesome to v3.0.5 (#3815)
Reviewed-on: vikunja/frontend#3815
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 15:03:14 +00:00
d1882e9c3f fix(deps): update dependency vue-i18n to v9.6.5 (#3807)
Reviewed-on: vikunja/frontend#3807
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 14:55:42 +00:00
974755ffc2 fix(deps): update sentry-javascript monorepo to v7.77.0 (#3805)
Reviewed-on: vikunja/frontend#3805
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 14:32:45 +00:00
Frederick [Bot]
f00d49cada [skip ci] Updated translations via Crowdin 2023-11-06 00:04:12 +00:00
Frederick [Bot]
e41ec4e8b2 [skip ci] Updated translations via Crowdin 2023-11-04 00:22:49 +00:00
218d72494a
fix: lint 2023-11-03 12:39:02 +01:00
bde212d432
fix(editor): change description when switching between tasks 2023-11-03 12:36:20 +01:00
64a8dd189b
fix(editor): always set mode to preview after save 2023-11-03 12:27:21 +01:00
ba766a29af
fix(editor): check for empty content 2023-11-03 12:22:38 +01:00
e02a106c64 chore(deps): update dev-dependencies (#3811)
Reviewed-on: vikunja/frontend#3811
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-03 11:06:43 +00:00
ccdc5d4868 fix(deps): update dependency @github/hotkey to v2.3.0 (#3810)
Reviewed-on: vikunja/frontend#3810
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-03 11:04:11 +00:00
9240739a4b chore(deps): update pnpm to v8.10.2 2023-11-01 17:10:19 +00:00
963d91c4d5 chore(deps): update dev-dependencies (#3806)
Reviewed-on: vikunja/frontend#3806
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-31 12:14:10 +00:00
f33d154b37 fix(deps): update dependency @github/hotkey to v2.2.0 (#3809)
Reviewed-on: vikunja/frontend#3809
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-31 11:42:47 +00:00
Frederick [Bot]
b12db63de0 [skip ci] Updated translations via Crowdin 2023-10-31 00:12:53 +00:00
Frederick [Bot]
a3e729a3c8 [skip ci] Updated translations via Crowdin 2023-10-30 00:04:31 +00:00
8e0ba555ed
fix(editor): check for almost empty editor value 2023-10-29 19:42:20 +01:00
9cf81e1478
fix(editor): use modelValue directly to update values in the editor 2023-10-29 19:39:38 +01:00
4350d78178 fix(deps): update dependency vue-i18n to v9.6.1 (#3803)
Reviewed-on: vikunja/frontend#3803
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-27 09:10:07 +00:00
cea27bb754 chore(deps): update dev-dependencies (#3802)
Reviewed-on: vikunja/frontend#3802
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-27 08:35:43 +00:00
4b7f8c265d fix(deps): update dependency axios to v1.6.0 (#3801)
Reviewed-on: vikunja/frontend#3801
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-27 08:35:05 +00:00
412e6e77b4 chore(deps): update dependency @types/node to v20 (#3796)
Reviewed-on: vikunja/frontend#3796
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 21:21:54 +00:00
45abdda680 fix(deps): update dependency vue-i18n to v9.6.0 (#3800)
Reviewed-on: vikunja/frontend#3800
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 21:21:20 +00:00
0b2188d72d fix(deps): update sentry-javascript monorepo to v7.75.1 (#3798)
Reviewed-on: vikunja/frontend#3798
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 21:20:56 +00:00
143a2a105d fix(deps): update dependency vue to v3.3.7 (#3799)
Reviewed-on: vikunja/frontend#3799
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 20:51:14 +00:00
68d18934d8 chore(deps): update dev-dependencies (#3793)
Reviewed-on: vikunja/frontend#3793
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 11:24:54 +00:00
cea3274a90 chore(deps): update dependency node (#3797)
Reviewed-on: vikunja/frontend#3797
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 10:55:57 +00:00
Frederick [Bot]
72f57a220d [skip ci] Updated translations via Crowdin 2023-10-24 00:04:26 +00:00
Frederick [Bot]
b94acfcc84 [skip ci] Updated translations via Crowdin 2023-10-23 00:04:16 +00:00
5f2787e18d
fix(task): use editor as preview first, then check for edit 2023-10-22 22:47:52 +02:00
2eac17ed57
fix(editor): commands list in dark mode 2023-10-22 19:58:25 +02:00
8d566c9371 fix(deps): update dependency @types/is-touch-device to v1.0.1 (#3786)
Reviewed-on: vikunja/frontend#3786
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 17:51:25 +00:00
ab5118b51b chore(deps): update pnpm to v8.9.2 2023-10-22 17:42:39 +00:00
0b294de132 fix(task): make sure the modal close button is not overlapped with the title field (#3256)
Resolves vikunja/frontend#3252
Reviewed-on: vikunja/frontend#3256
Co-authored-by: kolaente <k@knt.li>
Co-committed-by: kolaente <k@knt.li>
2023-10-22 17:40:50 +00:00
c1149273f9 fix(deps): update dependency @types/sortablejs to v1.15.4 (#3788)
Reviewed-on: vikunja/frontend#3788
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 17:31:22 +00:00
7496be5a44 fix(deps): update tiptap to v2.1.12 (#3790)
Reviewed-on: vikunja/frontend#3790
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 17:22:46 +00:00
a35b0f64a2 fix(deps): update dependency lowlight to v2.9.0 (#3789)
Reviewed-on: vikunja/frontend#3789
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 17:01:34 +00:00
6c2b30f8ef fix(deps): update dependency @types/lodash.clonedeep to v4.5.8 (#3787)
Reviewed-on: vikunja/frontend#3787
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 15:22:23 +00:00
daa720669a fix(deps): update sentry-javascript monorepo to v7.74.1 (#3778)
Reviewed-on: vikunja/frontend#3778
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 15:12:47 +00:00
26fc9b4e4f feat: move from easymde to tiptap editor (#2222)
Reviewed-on: vikunja/frontend#2222
2023-10-22 13:48:57 +00:00
37af478811
chore(editor): remove marked usages 2023-10-22 15:18:39 +02:00
22223a56bd
chore(editor): remove converting markdown 2023-10-22 15:12:36 +02:00
c367b70ccc
chore(editor): remove unused components 2023-10-22 15:12:23 +02:00
9103ad8505
chore(deps): remove unused dependencies 2023-10-22 14:46:21 +02:00
e4eaca82e1
fix(editor): add missing dependency 2023-10-22 14:29:48 +02:00
229beec1d1
fix(editor): lint 2023-10-22 14:24:10 +02:00
803f9c81c2
fix(editor): make tests work with changed structure 2023-10-22 14:21:28 +02:00
c6b123734b
feat(editor): add tests to check rendering of task description 2023-10-22 14:21:13 +02:00
0154b2a475
fix(editor): allow checking a checkbox even when the editor is set to read only 2023-10-22 13:26:01 +02:00
32ca8853bc
fix(editor): don't use global shortcut when anything is focused 2023-10-22 12:48:31 +02:00
f6d5cbcf6f
feat(editor): only load attachment images when rendering is done 2023-10-22 12:38:34 +02:00
d7503dc4a2
feat(editor): edit mode 2023-10-22 12:08:27 +02:00
632e3c5a0b
fix(editor): duplicate name for extension 2023-10-22 11:02:03 +02:00
c61f1a45fb
fix(editor): placeholder showing or not showing 2023-10-22 11:00:42 +02:00
2f3196ef86
fix(editor): duplicate name 2023-10-22 10:50:16 +02:00
2864854cd4
fix(editor): don't prevent typing editor focus shortcut when other instance of an editor is focused already 2023-10-22 10:47:13 +02:00
a453449fea
fix(editor): reset on empty 2023-10-22 10:41:34 +02:00
abb6630b4b
feat(editor): add comment when pressing ctrl enter 2023-10-22 10:40:12 +02:00
63c40b29b0
feat(editor): save when pressing ctrl enter 2023-10-22 10:38:53 +02:00
Frederick [Bot]
b7ff71ba76 [skip ci] Updated translations via Crowdin 2023-10-22 00:03:33 +00:00
19a78f1f75
fix(editor): lint 2023-10-21 19:52:56 +02:00
d6a41fa518
chore(editor): remove old editor component 2023-10-21 19:48:17 +02:00
859fc1e94e
feat(editor): edit shortcut to set focus into the editor 2023-10-21 19:46:46 +02:00
aa715dd9e1
chore(editor): cleanup unused options 2023-10-21 19:46:25 +02:00
daa2ed3b1c
feat(editor): allow passing placeholder down 2023-10-21 19:33:32 +02:00
1443e23f18
chore(editor): cleanup 2023-10-21 19:29:27 +02:00
34420b623c
fix(editor): use edit enable 2023-10-21 19:29:19 +02:00
80dc35eabb
fix(editor): actions styling 2023-10-21 19:29:00 +02:00
cb1d2b3834
fix(editor): always show placeholder when empty 2023-10-21 19:18:28 +02:00
0ef775e9b9
feat(editor): improve overall styling 2023-10-21 19:08:11 +02:00
a7e4e3adf9
feat(editor): add placeholder 2023-10-21 19:02:55 +02:00
d005875bbf
chore(editor): make sure all tiptap dependencies are updated as one 2023-10-21 18:57:17 +02:00
dc3ee112bd
chore(editor): cleanup 2023-10-21 18:47:04 +02:00
9b20dc1899
feat(editor): properly bubble changes when they are made 2023-10-21 18:40:30 +02:00
22103626b8
fix(editor): make checklist indicator work again 2023-10-21 18:18:17 +02:00
4f2d7b3ce2
feat(editor): add uploading an image on save 2023-10-21 18:03:59 +02:00
76d31c84ad
feat(editor): add tooltips for everything 2023-10-21 17:48:35 +02:00
66c37f10e0
chore(editor): cleanup 2023-10-21 14:10:26 +02:00
0b2aa723a6
feat(editor): open links when clicking on them 2023-10-21 14:07:39 +02:00
d75a963d08
feat(editor): add code highlighting 2023-10-21 14:06:47 +02:00
beefc1d5ef
feat(editor): add bubble menu 2023-10-21 14:02:53 +02:00
17c23d9463
feat(editor): make image upload work via slash command 2023-10-21 13:28:59 +02:00
02ab1b8c0a
feat(editor): add all slash commands 2023-10-21 13:00:12 +02:00
e81c98fe5b
chore(editor): format 2023-10-21 11:52:20 +02:00
3bf806f00c
fix(editor): add missing dependencies for commands 2023-10-21 11:46:02 +02:00
aea3f86a8f
feat(editor): add command list example
copied from 252acb32d2/demos/src/Experiments/Commands/Vue
2023-10-21 11:33:49 +02:00
5297208d92
feat(editor): move all editor related components into one folder 2023-10-21 11:15:17 +02:00
c84bcfddba
feat(editor): add proper description for all buttons 2023-10-21 11:10:43 +02:00
0772acbead
chore(editor): format 2023-10-21 10:53:41 +02:00
123c665d9d chore(deps): update dependency eslint to v8.52.0 (#3785)
Reviewed-on: vikunja/frontend#3785
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-21 08:08:26 +00:00
4f3efe4454
feat(editor): resolve and load attachment images from content 2023-10-20 23:03:38 +02:00
671c658868
fix(editor): actually populate loaded data into the editor 2023-10-20 22:52:21 +02:00
05bf7ccf0b
feat(editor): image upload 2023-10-20 22:43:10 +02:00
b76acb15c7 chore(deps): update dev-dependencies (major) (#3741)
Reviewed-on: vikunja/frontend#3741
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 19:34:11 +00:00
953361c480
chore(deps): update lockfile 2023-10-20 17:10:55 +02:00
8b60e5b2c8
fix(editor): add icons for clearing marks and nodes 2023-10-20 17:10:06 +02:00
faf93a6088
feat(editor): enable table 2023-10-20 17:10:06 +02:00
8e07d9647a
chore(editor): move checklist to the other lists 2023-10-20 17:10:06 +02:00
08959fdb77
fix(editor): focus state 2023-10-20 17:10:06 +02:00
e716fd1bf9
fix(editor): list styling 2023-10-20 17:10:06 +02:00
63865028b8
feat(editor): make task list work 2023-10-20 17:10:06 +02:00
e760ce45e4
fix(editor): checklist button icon 2023-10-20 17:10:06 +02:00
af9eb358ee
fix(editor): permission check for table editing 2023-10-20 17:10:06 +02:00
ddcf6bf0a5
fix(editor): image button icon 2023-10-20 17:10:06 +02:00
9c71e30efe
chore(editor): add break icon 2023-10-20 17:10:06 +02:00
c58ad47782
chore(editor): use typed props definition 2023-10-20 17:10:05 +02:00
ca0d9e6bd5
chore(editor): add horizontal line icon 2023-10-20 17:10:05 +02:00
ad3234b19f
chore(deps): update dependencies 2023-10-20 17:10:05 +02:00
24b8915983
wip: tiptap editor 2023-10-20 17:10:05 +02:00
01c2acdf34
chore(deps): update flake 2023-10-20 17:09:55 +02:00
ff2b4b8bf4
feat(notifications): add option to mark all as read 2023-10-20 16:52:03 +02:00
d73c62a424
fix(task): correctly build task identifier 2023-10-20 16:52:03 +02:00
cac41a1c86 fix(deps): update dependency vue to v3.3.6 (#3784)
Reviewed-on: vikunja/frontend#3784
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 14:01:41 +00:00
aeed4b3a3b
fix(settings): allow removing the default project via settings 2023-10-20 16:01:22 +02:00
8992caadf9 fix(deps): update dependency marked to v9.1.2 (#3774)
Reviewed-on: vikunja/frontend#3774
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 13:17:59 +00:00
b2b423aee5 chore(deps): update dependency node to v18.18.2 2023-10-20 12:08:04 +00:00
5d991e539b feat: webhooks (#3783)
Reviewed-on: vikunja/frontend#3783
2023-10-20 11:56:59 +00:00
accde483cb fix(deps): update dependency vue to v3.3.5 (#3782)
Reviewed-on: vikunja/frontend#3782
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 11:38:11 +00:00
2d5e560b74 chore(deps): update dev-dependencies (#3780)
Reviewed-on: vikunja/frontend#3780
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 11:37:50 +00:00
5d91134b48
fix(tasks): use mousedown event instead of click to close the task popup
Resolves vikunja/frontend#3779
2023-10-20 13:14:46 +02:00
0e5415a2c9
fix(webhooks): styling 2023-10-20 12:32:55 +02:00
779aad1b2d
feat(webhooks): add form validation 2023-10-20 12:32:46 +02:00
3d2fe4cf65
feat(webhooks): add webhook management form 2023-10-18 20:12:48 +02:00
c38421466b chore(deps): update dev-dependencies (#3776)
Reviewed-on: vikunja/frontend#3776
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-18 15:44:47 +00:00
Frederick [Bot]
df09bca010 [skip ci] Updated translations via Crowdin 2023-10-18 00:08:50 +00:00
b9717f504d
feat(i18n): add arabic to list of selectable languages 2023-10-17 18:44:08 +02:00
Frederick [Bot]
bb7c4f40a0 [skip ci] Updated translations via Crowdin 2023-10-17 00:09:16 +00:00
Frederick [Bot]
0b9ef50f80 [skip ci] Updated translations via Crowdin 2023-10-16 00:36:16 +00:00
Frederick [Bot]
cd295960a4 [skip ci] Updated translations via Crowdin 2023-10-14 00:08:49 +00:00
eb591fdd3c chore(deps): update dev-dependencies (#3769)
Reviewed-on: vikunja/frontend#3769
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-13 13:59:06 +00:00
23e1899fce fix(deps): update dependency @github/hotkey to v2.1.1 (#3770)
Reviewed-on: vikunja/frontend#3770
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-13 13:32:18 +00:00
22968ba639 fix(deps): update dependency pinia to v2.1.7 (#3771)
Reviewed-on: vikunja/frontend#3771
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-13 13:17:47 +00:00
b345f0ad61 fix(deps): update sentry-javascript monorepo to v7.74.0 (#3772)
Reviewed-on: vikunja/frontend#3772
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-13 12:27:20 +00:00
Frederick [Bot]
4df34701ab [skip ci] Updated translations via Crowdin 2023-10-13 00:08:52 +00:00
a5f7487bd0 fix(deps): update dependency marked to v9.1.1 (#3768)
Reviewed-on: vikunja/frontend#3768
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 21:42:37 +00:00
ae001c6ca7
fix(user): allow openid users to request their deletion
Resolves https://community.vikunja.io/t/delete-user-not-possible-when-using-oidc/1689/4
2023-10-11 19:07:11 +02:00
f0b340a9c7
feat(task): save currently opened task with control/meta + s 2023-10-11 17:44:17 +02:00
40538df392 fix(deps): update dependency @github/hotkey to v2.1.0 (#3766)
Reviewed-on: vikunja/frontend#3766
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 09:01:48 +00:00
fc17b16c60 chore(deps): update dependency sass to v1.69.2 (#3767)
Reviewed-on: vikunja/frontend#3767
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 07:34:29 +00:00
6c59b4e2d2 fix(deps): update dependency highlight.js to v11.9.0 (#3763)
Reviewed-on: vikunja/frontend#3763
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-10 19:21:25 +00:00
e8a38ed482 fix(deps): update vueuse to v10.5.0 (#3762)
Reviewed-on: vikunja/frontend#3762
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-10 18:28:10 +00:00
f5604dcac6 chore(deps): update dependency node to v18.18.1 2023-10-10 16:24:22 +00:00
049c644959 chore(deps): update dev-dependencies (#3761)
Reviewed-on: vikunja/frontend#3761
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-10 16:23:29 +00:00
07b1e9a6b7
chore: add pr lockdown 2023-10-10 18:02:40 +02:00
7aedf6ee1f chore(deps): update pnpm to v8.9.0 2023-10-09 12:09:54 +00:00
bc9bfe3300 fix(deps): update dependency marked to v9.1.0 (#3760)
Reviewed-on: vikunja/frontend#3760
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-05 07:06:20 +00:00
c2005c6c71 chore(deps): update dev-dependencies (#3757)
Reviewed-on: vikunja/frontend#3757
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-04 06:48:23 +00:00
d7cbade64e chore(deps): update node.js to v20.8 (#3756)
Reviewed-on: vikunja/frontend#3756
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-03 14:59:12 +00:00
06b00b77ed fix(deps): update dependency dompurify to v3.0.6 (#3754)
Reviewed-on: vikunja/frontend#3754
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-03 14:58:26 +00:00
f2392cef7e fix(deps): update dependency vue-router to v4.2.5 (#3755)
Reviewed-on: vikunja/frontend#3755
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-03 14:19:27 +00:00
e6f2b36d88 fix(deps): update dependency dayjs to v1.11.10 (#3753)
Reviewed-on: vikunja/frontend#3753
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-03 14:16:01 +00:00
7d2fcd26f2 chore(deps): update lockfile 2023-10-03 13:34:11 +00:00
369e22f224 fix(deps): update dependency ufo to v1.3.1 2023-10-03 13:34:11 +00:00
0ff5b90ebd chore(deps): update lockfile 2023-10-03 12:16:07 +00:00
e89245e42d fix(deps): update dependency @infectoone/vue-ganttastic to v2.2.0 2023-10-03 12:16:07 +00:00
35717a1e29 fix(deps): update dependency @kyvg/vue3-notification to v3 2023-10-03 12:16:07 +00:00
e46cf2fa1b fix(deps): update dependency vue-i18n to v9.5.0 2023-10-03 12:16:07 +00:00
1ad6d5a66b fix(deps): update dependency @intlify/unplugin-vue-i18n to v1 2023-10-03 12:16:07 +00:00
4754bb99f0 fix(deps): update dependency marked to v9 2023-10-03 12:16:07 +00:00
608e99fffc chore(deps): update pnpm to v8.8.0 2023-10-03 12:16:07 +00:00
d6741d19e3 fix(deps): update sentry-javascript monorepo to v7.73.0 2023-10-03 12:16:07 +00:00
ec52be0353 fix(deps): update dependency axios to v1.5.1 2023-10-03 12:16:07 +00:00
2d5ab4f5f0 chore(deps): update dependency node to v18.18.0 2023-10-03 11:45:51 +00:00
a71755e408 feat(gantt): implement dynamic sizing on small date ranges (#3750)
Reviewed-on: vikunja/frontend#3750
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Giacomo Rossetto <jackyman_cs4@live.it>
Co-committed-by: Giacomo Rossetto <jackyman_cs4@live.it>
2023-10-03 11:30:24 +00:00
66c7a05cdb
fix(project): correctly show project color next to project title in list view
Resolves https://community.vikunja.io/t/color-bubbles-not-showing-after-import/1648
2023-09-29 20:46:02 +02:00
287daf9125
fix(auth): silently discard invalid auth tokens and log the user out 2023-09-29 10:38:00 +02:00
8507808058
fix(tasks): ignore empty lines when adding multiple tasks at once
Resolves vikunja/frontend#3732 (comment)
2023-09-29 10:06:38 +02:00
93c155dd2f
fix(quick actions): always open quick actions with hotkey, even if other inputs are focused
Resolves vikunja/frontend#3743
2023-09-29 09:48:06 +02:00
b1c4748969
fix(background): unsplash author credit in dark mode 2023-09-29 09:33:30 +02:00
0887860b2a
fix(task): priority label spacing 2023-09-29 09:32:00 +02:00
0b1c8ed4dd
fix(attachments): layout and coloring in dark mode 2023-09-29 09:28:51 +02:00
3988a3f9f8
fix(task): correct spacing to task and project title 2023-09-29 09:19:17 +02:00
Frederick [Bot]
11b65e844c [skip ci] Updated translations via Crowdin 2023-09-28 00:08:46 +00:00
5c23343172 chore(deps): update dev-dependencies (#3747)
Reviewed-on: vikunja/frontend#3747
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-27 07:34:49 +00:00
01a4335c7c chore(deps): update node.js to v20.7 (#3736)
Reviewed-on: vikunja/frontend#3736
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-27 07:34:22 +00:00
Frederick [Bot]
4a2ecf5fe7 [skip ci] Updated translations via Crowdin 2023-09-24 00:29:29 +00:00
0235b14997 chore(deps): update dev-dependencies (#3746)
Reviewed-on: vikunja/frontend#3746
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-15 11:59:29 +00:00
8eafa23269 chore(deps): update dev-dependencies (#3740)
Reviewed-on: vikunja/frontend#3740
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-13 12:35:39 +00:00
5c95a721f4
fix(filter): don't show other filters in project selection in saved filter
Resolves https://github.com/go-vikunja/frontend/issues/125
2023-09-13 11:41:47 +02:00
a6eb804fae
fix(gantt): open task with double click from the gantt chart 2023-09-12 15:12:16 +02:00
Frederick [Bot]
09ffd9414b [skip ci] Updated translations via Crowdin 2023-09-08 00:29:37 +00:00
b126a7f7ff
fix(navigation): don't hide color bubble in navigation on touch devices
Related discussion: https://community.vikunja.io/t/board-color-sticker/1607
2023-09-07 16:36:11 +02:00
f256fc3843
fix(list view): align nested subtasks with the parent text 2023-09-07 13:47:52 +02:00
e41712647d
feat(list view): show subtasks nested
Resolves vikunja/frontend#363
2023-09-07 13:43:15 +02:00
Frederick [Bot]
842e2c2811 [skip ci] Updated translations via Crowdin 2023-09-07 00:28:16 +00:00
2d61a349ac
feat(task): immediately set focus on the task search input when opening the related tasks menu 2023-09-06 18:50:38 +02:00
54c527c23f
feat(tasks): make the whole task in list view clickable
Resolves vikunja/frontend#3172
Closes vikunja/frontend#3176
Closes vikunja/frontend#3180
2023-09-06 18:31:30 +02:00
4d8c6622d2
fix(ci): use correct secret key to push 2023-09-06 18:03:09 +02:00
3f3d4b1682
feat(labels): assign random color when creating labels
Resolves F-591
Related discussion: https://community.vikunja.io/t/assign-a-random-color-to-a-new-label/348/7
2023-09-06 17:10:36 +02:00
9c46d064ac
feat(quick add magic): allow using the project identifier via quick add magic
Related discussion: https://community.vikunja.io/t/using-shorter-list-names-in-quick-add-magic/895
2023-09-06 16:51:23 +02:00
0d3143d465
fix(quick add magic): headline 2023-09-06 16:45:12 +02:00
337c3e5e3e
fix: lint 2023-09-06 16:31:07 +02:00
3bb5308141
feat(task): group related task action buttons 2023-09-06 16:30:00 +02:00
3fec92283b
fix(task): priority label sizing and positioning in different environments 2023-09-06 15:58:52 +02:00
beb016400e
feat(task): move task priority to the front when showing tasks inline 2023-09-06 15:53:40 +02:00
7746d39161
fix(task): remove wrong repeat types
Repeating "monthly" or "yearly" was never what people expected, only 30 or 365 days which is not always correct. This change removes these settings since the repeating modes will be re-done anyway.

Related to vikunja/frontend#3585 (comment)
2023-09-06 15:41:48 +02:00
b187e8c1b6
fix(ci): pin used node version to 20.5 to avoid build issues
Related https://github.com/vitejs/vite/issues/14299
2023-09-06 15:33:04 +02:00
0ecda46af9
chore(deps): update dependencies 2023-09-06 15:30:00 +02:00
59dc927b5c
feat(i18n): update translations only once a day 2023-09-06 15:24:44 +02:00
a13953ee14
fix(i18n): add upload files config 2023-09-06 15:22:51 +02:00
a4b836d395
feat(i18n): run translation update directly 2023-09-06 15:19:32 +02:00
16b46b0f4d
feat(i18n): update crowdin sync to use v2 api 2023-09-06 15:18:27 +02:00
184110b986
fix(gantt): update the gantt view when switching between projects
Resolves https://community.vikunja.io/t/listing-subprojects-tasks/1567/5
2023-09-06 13:25:27 +02:00
1918947c0b
fix(tasks): reset page number when applying filters
Resolves https://community.vikunja.io/t/when-filter-conditions-change-pages-arent-updated-according-to-new-list-length/1601
2023-09-06 10:50:52 +02:00
4e5823183e
fix(tasks): update api route 2023-09-06 10:41:39 +02:00
b9e17ea870
fix(api tokens): show a token after it was created 2023-09-06 09:59:27 +02:00
Frederick [Bot]
a8a6ec5ab0 [skip ci] Updated translations via Crowdin 2023-09-06 00:29:43 +00:00
Frederick [Bot]
3e9b872894 [skip ci] Updated translations via Crowdin 2023-09-05 00:29:24 +00:00
c4adcf4655
chore: include version json string in release zip 2023-09-04 22:19:37 +02:00
b1fe3fe29b
fix: don't render route modal when no properties are defined 2023-09-04 21:33:50 +02:00
5720a86bc3
fix(task): don't reload the kanban board when opening a task 2023-09-04 21:01:42 +02:00
86eff7d49e
fix(task): don't reload the kanban board when opening a task 2023-09-04 20:27:55 +02:00
7a9aa7771b
fix(tasks): play pop sound directly and not from store
This solves two problems:

1. Previously, changing anything on a done task would play the pop sound all the time, because the store only knew the new done status was "done" and not if it was done previously already.
2. Safari will prevent playing a sound without user interaction. This means the user has to interact directly with the method playing the sound which was not the case when the sound was played from the store.

Resolves vikunja/frontend#3292
2023-09-04 20:14:43 +02:00
abbc11528e
feat(tasks): update due date text every minute
Related discussion: https://community.vikunja.io/t/text-describing-time-past-due-date-is-never-refreshed/1376/3
2023-09-04 14:00:22 +02:00
725fd1ad46
feat: improve error message for invalid API url
Resolves vikunja/frontend#3680
2023-09-04 13:37:17 +02:00
44754fac0f
chore(ci): sign drone config 2023-09-04 13:11:59 +02:00
7f2d92138e
fix: lint 2023-09-04 13:11:31 +02:00
95be0d1d32
fix(build): don't download Puppeteer when building for prod 2023-09-04 13:07:48 +02:00
f63c39a578
feat(assignees): improve avatar list consistency
Resolves vikunja/frontend#3354
2023-09-04 13:03:39 +02:00
270e32290a
fix(quick add magic): ignore common task indention when adding multiple tasks at once
Resolves vikunja/frontend#3732
2023-09-04 11:24:10 +02:00
9cf8696b84
fix(docker): set correct default value for custom logo url 2023-09-04 10:22:44 +02:00
Frederick [Bot]
b97e13b6b4 [skip ci] Updated translations via Crowdin 2023-09-04 00:28:15 +00:00
04ba1011cc feat: add setting for default bucket
Reviewed-on: vikunja/frontend#3735
2023-09-03 15:14:44 +00:00
52c0efe0ce
feat(kanban): add icon for bucket collapse 2023-09-03 16:32:29 +02:00
c803020537
feat(kanban): add setting for default bucket 2023-09-03 16:32:29 +02:00
3373b5fc45
feat(kanban): save done bucket with project instead of bucket 2023-09-03 16:32:29 +02:00
f6d1db3595
fix: tests 2023-09-03 16:30:36 +02:00
Frederick [Bot]
ce6f099912 [skip ci] Updated translations via Crowdin 2023-09-03 00:29:23 +00:00
ed8fb71ff0
feat: add demo mode warning message
Resolves vikunja/frontend#2453
2023-09-01 18:09:19 +02:00
28f2551d87 feat: api tokens
Reviewed-on: vikunja/frontend#3733
2023-09-01 14:34:56 +00:00
cec480ad80
fix(api tokens): lint 2023-09-01 15:59:16 +02:00
830a3745ba
feat(api tokens): show warning if token has expired 2023-09-01 13:32:00 +02:00
49104c65b6
fix(api tokens): expiry of tokens in a number of days 2023-09-01 13:28:32 +02:00
984978fe6d
feat(api tokens): format permissions and groups human-readable 2023-09-01 13:25:37 +02:00
bd7b973559
feat(api tokens): add deleting api tokens 2023-09-01 13:18:00 +02:00
0bb85870db
feat(api tokens): allow custom selection of expiry dates 2023-09-01 13:07:20 +02:00
021f92303d
feat(api tokens): validate title field when creating a new token 2023-09-01 12:56:23 +02:00
e47ad021a3
feat(api tokens): add token creation form 2023-09-01 12:47:32 +02:00
a20eef2453
feat(api tokens): add basic api token overview 2023-09-01 11:15:48 +02:00
Frederick [Bot]
7b57b10804 [skip ci] Updated translations via Crowdin 2023-08-31 00:29:36 +00:00
Frederick [Bot]
83a7032b6f [skip ci] Updated translations via Crowdin 2023-08-30 00:29:17 +00:00
49261a6fcc chore(deps): update dev-dependencies (#3726)
Reviewed-on: vikunja/frontend#3726
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-08-29 11:59:08 +00:00
5630c90dee
fix(task): show related tasks form with shortcut even when there are already other related tasks
Resolves https://github.com/go-vikunja/frontend/issues/122
2023-08-29 13:57:12 +02:00
47d589002c feat: quick actions improvments
Reviewed-on: vikunja/frontend#3728
2023-08-29 11:24:00 +00:00
99e2161c09
fix: lint 2023-08-29 12:46:30 +02:00
20f61baf03
fix(quick actions): search for tasks within a project when specifying a project with quick add magic 2023-08-29 12:45:05 +02:00
4e6b99544e
fix(quick actions): don't show projects when searching for labels or tasks 2023-08-29 12:38:59 +02:00
d57e1909c4
feat(quick actions): show labels as labels and tasks with all of their details 2023-08-29 12:33:43 +02:00
99d8fbdfa7
feat(quick actions): show tasks for a label when selecting it 2023-08-29 11:11:37 +02:00
442d0342a9
fix(quick actions): project search 2023-08-29 10:08:47 +02:00
a4b369470a
fix(quick actions): invalid class prop 2023-08-29 09:57:13 +02:00
0ca73e0851
fix(quick actions): always search for projects 2023-08-29 09:41:53 +02:00
9fc829115f
fix(quick actions): project filter 2023-08-29 09:34:08 +02:00
1e19548563
chore(quick actions): format 2023-08-29 09:33:56 +02:00
c327d86a71
feat(quick actions): show task identifier 2023-08-29 09:33:41 +02:00
3044560759
feat(quick actions): show done tasks last 2023-08-29 09:21:11 +02:00
c3f85fcb19
chore: format 2023-08-29 09:19:52 +02:00
53434952d3 chore(deps): update dev-dependencies (#3721)
Reviewed-on: vikunja/frontend#3721
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-08-27 15:27:00 +00:00
e9b0640660 fix(deps): update dependency @vueuse/core to v10.4.0 (#3723)
Reviewed-on: vikunja/frontend#3723
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-08-27 15:00:38 +00:00
ae57e5d314
chore(deps): update pnpm to v8.7.0 2023-08-27 16:33:22 +02:00
6e7928b2e4
fix(i18n): hungarian translation 2023-08-27 16:32:06 +02:00
47639b00f8
feat(i18n): add hungarian translation for selection 2023-08-27 10:28:31 +02:00
Frederick [Bot]
e63cecceca [skip ci] Updated translations via Crowdin 2023-08-27 00:29:16 +00:00
Frederick [Bot]
55e2e323ed [skip ci] Updated translations via Crowdin 2023-08-26 00:29:30 +00:00
f7e22c8c56
fix(auth): correctly redirect the user to the last visited page after login
Resolves vikunja/frontend#3682
2023-08-24 12:15:45 +02:00
a9fb306e46
fix(i18n): fall back to browser language if the configured user language is invalid 2023-08-24 11:37:23 +02:00
58a1f46668
fix(projects): don't suggest to create a new task in an empty filter 2023-08-24 11:32:28 +02:00
6cbbe17bd8
fix(filters): don't allow marking a filter as favorite 2023-08-24 11:30:57 +02:00
c01957aae2
fix: lint 2023-08-24 11:27:31 +02:00
1ad03877fb
fix(menu): separate favorite and saved filter projects from other projects
Resolves vikunja/frontend#3710
Resolves https://github.com/go-vikunja/frontend/issues/119
2023-08-24 11:27:20 +02:00
fc72a82a2a
fix(task): duplicate attribute 2023-08-24 11:18:17 +02:00
63ef09b020
fix(filters): incorrect translation string 2023-08-24 11:18:03 +02:00
DIMITRIOS CHRYSOCHERIS
311b1d7594
chore: improve checking for API url '/' suffix (#121) 2023-08-23 21:56:08 +02:00
cade3df3e9 feat: allow custom logo via environment variable (#3685)
Related discussion: https://community.vikunja.io/t/change-vikunja-logo-and-color-scheme/621

Reviewed-on: vikunja/frontend#3685
Reviewed-by: konrad <k@knt.li>
Co-authored-by: davidangel <david@davidangel.net>
Co-committed-by: davidangel <david@davidangel.net>
2023-08-23 16:13:29 +00:00
37975c1931 chore(deps): update lockfile 2023-08-23 06:37:45 +00:00
0d500182e7 chore(deps): update dev-dependencies 2023-08-23 06:37:45 +00:00
Frederick [Bot]
f647d6e9b4 [skip ci] Updated translations via Crowdin 2023-08-23 00:28:13 +00:00
dbed4caca7 fix(deps): update dependency ufo to v1.2.0 2023-08-21 11:06:04 +00:00
6d79c9b2ed chore(deps): update pnpm to v8.6.12 2023-08-21 11:03:59 +00:00
24f0822a12 fix(deps): update dependency @vueuse/core to v10.3.0 2023-08-21 11:03:34 +00:00
f3ba778fd3 fix(deps): update dependency pinia to v2.1.6 2023-08-21 11:03:08 +00:00
55a7255728 fix(deps): update font awesome to v6.4.2 2023-08-21 10:58:33 +00:00
2b47e5faec chore(deps): update node.js to v18.17.1 2023-08-21 10:58:19 +00:00
9f82ec4162
chore: update lockfile 2023-08-21 12:22:58 +02:00
64c90c7fe8
chore: update flake 2023-08-21 12:22:30 +02:00
9fe3d2b2bc chore(deps): update dev-dependencies 2023-08-21 09:06:05 +00:00
Frederick [Bot]
0b1ec9f287 [skip ci] Updated translations via Crowdin 2023-08-16 00:09:52 +00:00
Frederick [Bot]
baff1c6fc9 [skip ci] Updated translations via Crowdin 2023-08-15 00:08:33 +00:00
Frederick [Bot]
ac3f0cc266 [skip ci] Updated translations via Crowdin 2023-08-07 00:08:30 +00:00
7e1cfebf6a chore(deps): update dependency @types/node to v18.17.0 2023-07-23 01:29:55 +00:00
Frederick [Bot]
88203e8b7d [skip ci] Updated translations via Crowdin 2023-07-23 00:09:21 +00:00
d466d50712 chore(deps): update dev-dependencies 2023-07-22 01:04:58 +00:00
cf945f2841 fix(deps): update sentry-javascript monorepo to v7.60.0 2023-07-21 14:06:11 +00:00
74df69fc94 chore(deps): update dev-dependencies 2023-07-21 12:04:48 +00:00
Frederick [Bot]
563ee8f5bc [skip ci] Updated translations via Crowdin 2023-07-21 00:09:38 +00:00
026db7acad chore(deps): update node.js to v18.17.0 2023-07-20 07:24:20 +00:00
61e97bfe1c fix(deps): update sentry-javascript monorepo to v7.59.3 2023-07-19 17:04:49 +00:00
6530d26b82 chore(deps): update pnpm to v8.6.9 2023-07-18 11:04:21 +00:00
1e24fe8bab fix(deps): update sentry-javascript monorepo to v7.59.2 2023-07-18 09:04:49 +00:00
f786c2b8a2 chore(deps): update dev-dependencies 2023-07-18 08:50:37 +00:00
e596e2c3bc fix(deps): update sentry-javascript monorepo to v7.59.1 2023-07-18 08:04:48 +00:00
Frederick [Bot]
a5e49d9417 [skip ci] Updated translations via Crowdin 2023-07-18 00:08:41 +00:00
668b910190 chore(deps): update pnpm to v8.6.8 2023-07-17 10:04:41 +00:00
2bdc532f89 fix(deps): update dependency codemirror to v5.65.14 2023-07-17 08:04:36 +00:00
933c7d8acc chore(deps): update dev-dependencies 2023-07-16 04:28:21 +00:00
Frederick [Bot]
253e716390 [skip ci] Updated translations via Crowdin 2023-07-16 00:09:30 +00:00
d19a5d9714 chore(deps): update dev-dependencies 2023-07-15 00:04:50 +00:00
90cad1c8dd chore(deps): update dev-dependencies 2023-07-14 01:09:00 +00:00
Frederick [Bot]
057017c8eb [skip ci] Updated translations via Crowdin 2023-07-14 00:08:42 +00:00
d7ce8dd320
fix(quick add magic): repeating intervals in words
Resolves vikunja/frontend#3676
2023-07-13 18:20:30 +02:00
25b110ce48
fix(quick add magic): annually and variants spelling
Related to vikunja/frontend#3676
2023-07-13 18:05:19 +02:00
33fe5e4f20 fix(deps): update sentry-javascript monorepo to v7.58.1 2023-07-13 12:04:38 +00:00
129ef769a3 fix(deps): update sentry-javascript monorepo to v7.58.0 2023-07-13 06:47:22 +00:00
9030a9f7c1 chore(deps): update dev-dependencies 2023-07-13 02:05:04 +00:00
Frederick [Bot]
3748a496d5 [skip ci] Updated translations via Crowdin 2023-07-13 00:08:55 +00:00
890e7e1f52 chore(deps): update dependency vite to v4.4.3 2023-07-12 00:05:46 +00:00
9e0f2b0249 chore(deps): update dev-dependencies 2023-07-11 11:04:54 +00:00
9a34c522b2 fix(deps): update dependency dompurify to v3.0.5 2023-07-11 10:24:42 +00:00
60dd698fad chore(deps): update dev-dependencies to v6 2023-07-11 10:05:43 +00:00
15ecafdf04
fix: don't try to load buckets for project id 0 2023-07-11 10:42:20 +02:00
8902c15f7e
fix: correctly resolve kanban board in the background when moving a task
Resolves F-951
2023-07-10 18:10:14 +02:00
d5358793de
chore: provide better error messages when refreshing user info fails 2023-07-10 12:20:40 +02:00
33798b8d88 chore(deps): update pnpm to v8.6.7 2023-07-10 06:43:02 +00:00
c686e8677b chore(deps): update dependency caniuse-lite to v1.0.30001514 2023-07-10 00:05:28 +00:00
5acc1696a9 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.12.2 2023-07-08 19:04:46 +00:00
c4976b6a22 chore(deps): update dependency vite to v4.4.2 2023-07-08 00:06:23 +00:00
d88ff594e1 fix(deps): update dependency marked to v5.1.1 2023-07-07 15:04:29 +00:00
66f0df0333
chore: release preparation 2023-07-07 13:38:53 +02:00
b742c55287
fix(projects): update project duplicate api definitions 2023-07-07 13:00:48 +02:00
82c9a91d39
fix(project): don't try to read title of undefined project 2023-07-07 12:43:40 +02:00
cd820a6cb2
fix(project): duplicate a project without new parent 2023-07-07 12:42:26 +02:00
2c4da79c1b
fix(task): make an attachment cover image 2023-07-07 12:36:27 +02:00
c4f6465569 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.12.1 2023-07-07 08:04:45 +00:00
7d84601f6d chore(deps): update dev-dependencies 2023-07-07 06:04:40 +00:00
8f94b7490c fix(deps): update dependency vue-router to v4.2.4 2023-07-06 17:04:48 +00:00
cbce7cd142 chore(deps): update dev-dependencies 2023-07-06 05:12:56 +00:00
28e775be42 chore(deps): update dev-dependencies 2023-07-06 02:04:50 +00:00
24ad2f892d
feat: improve handling of an invalid api url
Resolves vikunja/frontend#1964
2023-07-05 17:46:52 +02:00
7c1934aad0
fix: don't try to set config from non-json responses 2023-07-05 17:37:07 +02:00
ae2b0f97c4
fix: make update available button use the correct text color all the time 2023-07-05 17:26:13 +02:00
70e0696300 fix(deps): update dependency vue-router to v4.2.3 2023-07-05 14:05:11 +00:00
Frederick [Bot]
60e49468cf [skip ci] Updated translations via Crowdin 2023-07-05 00:09:35 +00:00
10e566164e chore(deps): update dev-dependencies 2023-07-04 06:13:08 +00:00
3c1b54c1a3 chore(deps): update dev-dependencies 2023-07-04 00:05:54 +00:00
b80c6cf326 chore(deps): update pnpm to v8.6.6 2023-07-03 14:04:18 +00:00
d2e6ab4505
chore(deps): update all dev dependencies at once per day 2023-07-03 11:59:56 +02:00
32ed4c7da9 chore(deps): update dependency vitest to v0.32.3 2023-07-03 09:48:54 +00:00
70ae19a903 chore(deps): update dependency css-has-pseudo to v6 2023-07-03 09:05:03 +00:00
cc2e0e79d3 chore(deps): update dependency caniuse-lite to v1.0.30001511 2023-07-03 03:04:36 +00:00
Frederick [Bot]
49bdd00133 [skip ci] Updated translations via Crowdin 2023-07-02 00:28:57 +00:00
43eb742352 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.12.0 2023-07-01 18:04:39 +00:00
34b6692f25 chore(deps): update dependency esbuild to v0.18.11 2023-07-01 07:04:37 +00:00
152aefd365 chore(deps): update dependency eslint to v8.44.0 2023-07-01 04:37:27 +00:00
2297872879 fix(deps): update dependency dayjs to v1.11.9 2023-07-01 04:37:17 +00:00
75ca7ecd61 chore(deps): update dependency @types/node to v18.16.19 2023-07-01 01:04:56 +00:00
Frederick [Bot]
0909d2cfe5 [skip ci] Updated translations via Crowdin 2023-07-01 00:28:02 +00:00
a60662b72b chore(deps): update dependency rollup to v3.26.0 2023-06-30 05:04:32 +00:00
Frederick [Bot]
478b2c043e [skip ci] Updated translations via Crowdin 2023-06-30 00:28:59 +00:00
6bc54d7a92 fix(deps): update dependency dompurify to v3.0.4 2023-06-29 17:04:46 +00:00
e8e664b256 chore(deps): update dependency typescript to v5.1.6 2023-06-29 01:04:35 +00:00
Frederick [Bot]
72fd932020 [skip ci] Updated translations via Crowdin 2023-06-29 00:27:59 +00:00
e5090b117f fix(deps): update sentry-javascript monorepo to v7.57.0 2023-06-28 16:04:38 +00:00
1b498a238c fix(deps): update dependency floating-vue to v2.0.0-beta.24 2023-06-28 14:04:36 +00:00
2a14325f62
feat(projects): allow setting a saved filter for tasks shown on the overview page
Resolves vikunja/api#1545
Resolves https://community.vikunja.io/t/customizable-overview-page/685
2023-06-28 15:20:43 +02:00
ac6c4cf2bc
fix(project): make sure the correct tasks are loaded when switching between projects
Resolves https://community.vikunja.io/t/filter-table-view-not-sorting/1416/3
2023-06-28 14:41:43 +02:00
66bad4b2b1 fix(deps): update dependency @vueuse/core to v10.2.1 2023-06-28 12:04:38 +00:00
cdac38eabc chore(deps): update dependency vue-tsc to v1.8.3 2023-06-28 10:04:55 +00:00
79918620c3 chore(deps): update dependency @tsconfig/node18 to v18 2023-06-28 05:25:57 +00:00
dd1ae53d00 chore(deps): update dependency typescript to v5.1.5 2023-06-27 23:05:05 +00:00
9894490616 chore(deps): update dependency esbuild to v0.18.10 2023-06-27 01:04:35 +00:00
Frederick [Bot]
a218eab609 [skip ci] Updated translations via Crowdin 2023-06-27 00:29:30 +00:00
c046bb95b3 chore(deps): update dependency rollup to v3.25.3 2023-06-26 21:04:31 +00:00
12ad0d2ed3 chore(deps): update dependency vue-tsc to v1.8.2 2023-06-26 19:04:34 +00:00
8e505b6f51 chore(deps): update typescript-eslint monorepo to v5.60.1 2023-06-26 18:04:37 +00:00
3589251f55 chore(deps): update dependency cypress to v12.16.0 2023-06-26 17:04:37 +00:00
b3b0d8d6e6 chore(deps): update pnpm to v8.6.5 2023-06-26 10:04:29 +00:00
06572e8f0a chore(deps): update dependency rollup to v3.25.2 2023-06-26 06:21:16 +00:00
fe7e06079e chore(deps): update dependency caniuse-lite to v1.0.30001508 2023-06-26 06:19:37 +00:00
c2b04a2b81 chore(deps): update pnpm to v8.6.4 2023-06-26 06:19:18 +00:00
d2148df6c8 chore(deps): update dependency esbuild to v0.18.9 2023-06-26 06:04:41 +00:00
Frederick [Bot]
305c5b32ee [skip ci] Updated translations via Crowdin 2023-06-24 00:28:58 +00:00
a446310986
fix(task): break long task titles after 4 lines only
Resolves https://github.com/go-vikunja/frontend/issues/84
Resolves https://community.vikunja.io/t/text-wrap-on-lists/1441
2023-06-23 14:28:06 +02:00
8a22d1811e
fix(link share): default share view should be list, not project 2023-06-23 14:18:52 +02:00
Frederick [Bot]
d34a872d40 [skip ci] Updated translations via Crowdin 2023-06-23 00:28:56 +00:00
06126de139
fix(i18n): typo 2023-06-22 06:14:18 +02:00
047435bb68 chore(deps): update dependency sass to v1.63.6 2023-06-22 03:35:29 +00:00
4be631888a chore(deps): update dependency @vue/test-utils to v2.4.0 2023-06-22 01:06:29 +00:00
Frederick [Bot]
be11397163 [skip ci] Updated translations via Crowdin 2023-06-22 00:29:37 +00:00
f3986c710b
feat(i18n): use chinese name for chinese translation 2023-06-21 20:16:03 +02:00
9c8266fb0d
feat(i18n): enable Japanese translation 2023-06-21 20:15:48 +02:00
7148b56eea
feat(i18n): enable Danish translation 2023-06-21 20:14:21 +02:00
ff6645d2ab
feat(i18n): enable Spanish translation 2023-06-21 20:12:56 +02:00
bd17afe466 chore(deps): update dependency @vitejs/plugin-legacy to v4.0.5 2023-06-21 14:05:23 +00:00
25bd26bea6
chore(editor): disable deprecated marked options 2023-06-21 15:12:45 +02:00
83c0ef4e8b
fix(project): set maxRight on projects after opening a task 2023-06-21 15:09:31 +02:00
f55c42f124 chore(deps): update dependency postcss-preset-env to v8.5.1 2023-06-21 08:13:39 +00:00
4189fdadd9 chore(deps): update dependency esbuild to v0.18.6 2023-06-21 05:43:09 +00:00
2670cecf70 chore(deps): update dependency sass to v1.63.5 2023-06-21 05:42:25 +00:00
e54b5e88cc chore(deps): update node.js to v18.16.1 2023-06-20 21:03:45 +00:00
03c6e343c2 chore(deps): update dependency cypress to v12.15.0 2023-06-20 19:05:45 +00:00
9acde8d017 fix(deps): update dependency pinia to v2.1.4 2023-06-20 14:48:20 +00:00
813d2b56a0
fix: don't try to map non-array data 2023-06-20 16:21:45 +02:00
1005182a50
feat(reminders): add e2e tests for task reminders 2023-06-20 15:56:39 +02:00
4eba9479b0
feat(link share): add e2e tests for link share hash 2023-06-20 15:56:13 +02:00
dbe1ad9353
fix: set and use correct type for destructured props 2023-06-20 15:24:02 +02:00
b6cd424aa3
fix: set vue-ignore 2023-06-20 15:22:19 +02:00
6651adf6de
chore(deps): update lockfile 2023-06-20 14:41:40 +02:00
3aa502e07d
fix: use props destructuring everywhere 2023-06-20 14:40:41 +02:00
78a268ab07
fix(building): let the compiler ignore props interface 2023-06-20 14:40:41 +02:00
4661c2e90e
fix(deps): update dependency vue to v3.3.4 2023-06-20 14:40:38 +02:00
fbfa265dbf fix(deps): update dependency vue to v3.3.4 2023-06-20 10:05:53 +00:00
c1ad1b0639 chore(deps): update dependency vue-tsc to v1.8.1 2023-06-20 09:05:22 +00:00
c27661107f
fix(project): correctly load background when switching from or to a project view
Resolves https://community.vikunja.io/t/background-does-not-load/1437
2023-06-20 10:54:13 +02:00
464cc0ed8c chore(deps): update dependency esbuild to v0.18.5 2023-06-20 05:38:25 +00:00
f8e2ef210f chore(deps): update typescript-eslint monorepo to v5.60.0 2023-06-20 01:06:33 +00:00
Frederick [Bot]
61379ed4c6 [skip ci] Updated translations via Crowdin 2023-06-20 00:29:35 +00:00
f1f99d065c fix(deps): update sentry-javascript monorepo to v7.56.0 2023-06-19 15:05:55 +00:00
da3eaf0d35 feat: persist link share auth rule in url hash (#3336)
Reviewed-on: vikunja/frontend#3336
2023-06-19 14:26:32 +00:00
34182b8bbb
fix: follow the happy path 2023-06-19 15:28:06 +02:00
20660564c1
feat: change the link share hash name 2023-06-19 15:28:06 +02:00
c2ffe3a9dc
feat: check link share auth from store instead 2023-06-19 15:28:06 +02:00
a33e2f6c00
chore: follow the happy path 2023-06-19 15:28:06 +02:00
0ce150af23
chore: move const 2023-06-19 15:28:06 +02:00
06a1ff6f4b
chore: reduce nesting 2023-06-19 15:28:06 +02:00
7c964c29d4
fix: return redirect 2023-06-19 15:28:06 +02:00
b9f0635d9f
feat: rename link share hash prefix 2023-06-19 15:28:05 +02:00
61baf02e26
chore: import const instead of redeclaring it 2023-06-19 15:28:05 +02:00
59b05e9836
chore: rename getRedirectRoute 2023-06-19 15:28:05 +02:00
f68bb2625e
feat: persist link share auth rule in url hash
This allows sharing links to a task directly. We're using hashes instead
of query parameters because hash values are usually not logged in access
logs.

With this change, when a user uses a link share, the link share hash
will be appended to all urls while browsing. When a link share hash is
encountered in the current url and the user is not authenticated, they
will be redirected to the link share auth page, get authenticated and
then get redirected to whatever url they were previously on.
2023-06-19 15:28:05 +02:00
25f9f5dceb chore(deps): update pnpm to v8.6.3 2023-06-19 13:05:05 +00:00
35d68619f4 chore(deps): update caniuse-and-related 2023-06-19 00:07:04 +00:00
929d4f4023
chore: catch error when trying to play pop sound
Safari does not allow playing sound without user interaction, so we'll just silently catch and ignore the error until we have a better solution.
2023-06-18 18:58:57 +02:00
a92eb31ab3
fix(settings): don't try to sort timezones if there are none 2023-06-18 18:53:01 +02:00
2006abd0a6
fix(task): call getting task identifier directly instead of using model function 2023-06-18 18:46:18 +02:00
e4504748c4 chore(deps): update dependency vite-plugin-sentry to v1.3.0 2023-06-18 15:29:34 +00:00
854228034d
chore(task): use ref for task instead of reactive 2023-06-18 17:02:52 +02:00
88ce29aa77
fix(sentry): use correct environment from vite env mode 2023-06-18 16:26:23 +02:00
0ca1b3a7f5
fix(sentry): don't fail the build when sentry upload fails 2023-06-18 16:22:49 +02:00
a118580704
fix: don't try to map data from empty responses 2023-06-18 16:10:46 +02:00
771aad5420
chore(sentry): ignore missing commits 2023-06-18 16:04:42 +02:00
411ae58e59
chore(ci): sign drone config 2023-06-18 15:44:16 +02:00
c3501f5060
chore(sentry): remove debug options 2023-06-18 15:41:49 +02:00
5ca31d00ee feat: add vite-plugin sentry (#1991)
Reviewed-on: vikunja/frontend#1991
2023-06-18 13:40:21 +00:00
1e2039325f
chore(ci): sign drone config 2023-06-18 15:23:56 +02:00
472cca8ab8
chore(sentry): remove sourcemaps after upload via plugin 2023-06-18 15:12:29 +02:00
2fad45e016
chore(sentry): use correct chunks option 2023-06-18 15:05:10 +02:00
68a137acf9
chore(sentry): only load sentry when enabled 2023-06-18 15:01:49 +02:00
95ba8b8a11
chore(sentry): alwys use the same version 2023-06-18 15:00:00 +02:00
96c9407414
feat(sentry): only load sentry when it's enabled 2023-06-18 14:49:25 +02:00
653415e764
chore(deps): install dependencies after rebase 2023-06-18 14:38:30 +02:00
73947f0ba4
feat: add vite-plugin sentry 2023-06-18 14:33:21 +02:00
389ca1b692 chore(deps): update dependency vue-tsc to v1.8.0 2023-06-18 08:30:20 +00:00
9c0e140e2e chore(deps): update dependency eslint to v8.43.0 2023-06-16 22:05:48 +00:00
51d08a1637 fix(deps): update dependency floating-vue to v2.0.0-beta.22 2023-06-16 19:05:33 +00:00
35de8a40d8
fix(docker): copy patches prior to installing dependencies so that the installation actually works 2023-06-16 20:35:08 +02:00
80772f7578
fix(ci): directly build docker images and not use releases to avoid caching issues 2023-06-16 20:07:43 +02:00
faa62985df
fix: correctly sync filters on upcoming tasks page
Resolves vikunja/frontend#3600
2023-06-16 19:49:43 +02:00
154d43a392
fix(reminders): don't assigne the task 2023-06-16 19:42:55 +02:00
7fe5565654 chore(deps): update dependency vitest to v0.32.2 2023-06-16 17:21:20 +00:00
1fcd1cdd4b
fix(reminders): assignment to const when changing a reminder 2023-06-16 19:20:40 +02:00
ba057f3527
feat(reminders): add preset two hours before due / start / end date 2023-06-16 19:12:07 +02:00
dd7b77e12d
feat(reminders): add on the due / start / end date as a reminder preset 2023-06-16 19:12:06 +02:00
3845a45934 chore(deps): update dependency vitest to v0.32.1 2023-06-16 16:41:52 +00:00
564808bd35 fix(deps): update dependency @vueuse/core to v10.2.0 2023-06-16 16:41:30 +00:00
c0a66e4746 fix(deps): update dependency floating-vue to v2.0.0-beta.21 2023-06-16 16:41:08 +00:00
28242cfb23 chore(deps): update dependency esbuild to v0.18.4 2023-06-16 16:05:32 +00:00
818fb2b524 chore(deps): update dependency esbuild to v0.18.3 2023-06-16 01:05:45 +00:00
Frederick [Bot]
ad95bdd039 [skip ci] Updated translations via Crowdin 2023-06-16 00:29:42 +00:00
3faed19298 chore(deps): update dependency @rushstack/eslint-patch to v1.3.2 2023-06-15 01:05:19 +00:00
9114a86813 chore(deps): update dependency postcss-preset-env to v8.5.0 2023-06-14 21:21:31 +00:00
bae9a5c9be fix(deps): update sentry-javascript monorepo to v7.55.2 2023-06-14 15:05:21 +00:00
fe2d6d4467 chore(deps): update dependency sass to v1.63.4 2023-06-14 01:05:12 +00:00
Frederick [Bot]
96acea90ed [skip ci] Updated translations via Crowdin 2023-06-14 00:29:44 +00:00
21c98d5166 chore(deps): update dependency histoire to v0.16.2 2023-06-13 17:14:09 +00:00
b3cb36c1e1 fix(deps): update sentry-javascript monorepo to v7.55.0 2023-06-13 15:06:33 +00:00
79ceaf6a2b
fix(task): repeat mode now saves correctly 2023-06-13 12:33:35 +02:00
5694b39489
feat(reminders): show resolved reminder time in a tooltip and properly bubble updated task down to the reminder component 2023-06-13 12:30:07 +02:00
32e5f9f757
fix(reminders): don't sync negative relative reminder amounts in ui 2023-06-13 12:10:10 +02:00
928b338cf2
fix(reminders): don't assume 30 days are always a month 2023-06-13 12:06:00 +02:00
1a792e0667
feat(reminders): only show relative reminders when there's a date to relate them to 2023-06-13 12:03:28 +02:00
6407644138 chore(deps): update dependency esbuild to v0.18.2 2023-06-13 05:54:06 +00:00
2db88b583b chore(deps): update dependency @types/node to v18.16.18 2023-06-13 03:05:22 +00:00
bef25c49d5
feat: new image for the unauthenticated views
There have been so many big changes lately, I think it's time for a new image.
2023-06-12 21:58:17 +02:00
f01ea20a38 chore(deps): update typescript-eslint monorepo to v5.59.11 2023-06-12 18:06:30 +00:00
3c9083b90d
feat: add message to add to home screen on mobile 2023-06-12 19:37:58 +02:00
169feaaf0f feat(user): persist frontend settings in the api (#3594)
Implements saving of frontend settings for 04e2c51fac.

Resolves https://github.com/go-vikunja/frontend/issues/105
Resolves vikunja/api#1250
Resolves vikunja/api#1452
Reviewed-on: vikunja/frontend#3594
2023-06-12 16:22:51 +00:00
5d59392566
fix: lint 2023-06-12 18:06:07 +02:00
6593380013
fix: load the correct language 2023-06-12 18:01:56 +02:00
69e94e58c4
fix: tests 2023-06-12 16:35:47 +02:00
cd8e497b24
fix(user): lint 2023-06-12 16:22:15 +02:00
aab2020e68
chore(user): cleanup 2023-06-12 16:20:46 +02:00
a050419fdf
fix(user): set the language when saving 2023-06-12 16:19:47 +02:00
f0c3980700
fix(user): fix flickering of default settings 2023-06-12 16:18:01 +02:00
68597c9709
feat(user): use user language from store after logging in 2023-06-12 16:08:31 +02:00
5325f6d7d9
feat(user): migrate color scheme settings to persistance in db 2023-06-12 15:57:18 +02:00
8c687350a0 chore(deps): update dependency esbuild to v0.18.1 2023-06-12 06:45:04 +00:00
bac679caf7 chore(deps): update dependency rollup to v3.25.1 2023-06-12 06:42:03 +00:00
4f8ff17138 chore(deps): update dependency caniuse-lite to v1.0.30001500 2023-06-12 06:05:17 +00:00
Frederick [Bot]
83e7138a18 [skip ci] Updated translations via Crowdin 2023-06-12 00:28:51 +00:00
8e44b87d07 chore(deps): update pnpm to v8.6.2 2023-06-11 20:04:39 +00:00
4b0022664a
feat(user): set default settings when loading persisted 2023-06-11 17:43:55 +02:00
d8ad934643
feat(user): save quick add magic mode in api 2023-06-11 17:37:49 +02:00
77ee1bfc3e
feat(user): migrate pop sound setting to store in api 2023-06-11 17:31:04 +02:00
8728647f00 chore(deps): update dependency vite-plugin-pwa to v0.16.4 2023-06-11 12:05:13 +00:00
bd7d09c17c
fix(repeat): prevent disappearing repeat mode settings when modes other than default repeat mode were selected
Resolves vikunja/frontend#3585
2023-06-11 09:48:54 +02:00
77bedbd1cf chore(deps): update dependency rollup to v3.25.0 2023-06-11 06:05:08 +00:00
2773612420 chore(deps): update dependency rollup-plugin-visualizer to v5.9.2 2023-06-10 19:05:24 +00:00
48cfdddff7
chore(reminders): remove reminderDates property 2023-06-10 19:05:22 +02:00
3f8e457d52 feat: edit relative reminders (#3248)
Reviewed-on: vikunja/frontend#3248
2023-06-10 17:04:09 +00:00
098b5fa2b1
fix(reminders): properly parse relative reminders which don't have an amount 2023-06-10 18:54:39 +02:00
5e4eb4a728
fix(reminders): custom relative highlight now only when a custom relative reminder was actually selected 2023-06-10 18:54:10 +02:00
8930f61548
fix(reminders): flatpickr styling improvements 2023-06-10 18:46:15 +02:00
9a736cf65f
fix(reminders): style flatpickr so that it blends in more 2023-06-10 18:39:33 +02:00
2677f6254d
feat(reminders): highlight which preset or custom date is selected 2023-06-10 17:35:50 +02:00
bfcb36e093
fix(reminders): align remove icon with the rest 2023-06-10 17:29:30 +02:00
9ec29cad30
fix: lint 2023-06-10 17:26:06 +02:00
c4f609a0c8 chore(deps): update dependency esbuild to v0.18.0 2023-06-10 15:00:24 +00:00
7e7535b860 chore(deps): update dependency @types/node to v18.16.17 2023-06-10 15:00:05 +00:00
df9181b34e chore(deps): update dependency rollup to v3.24.1 2023-06-10 14:59:45 +00:00
e6a56f2822 fix(deps): update dependency marked to v5.1.0 2023-06-10 04:06:05 +00:00
3633d68269 chore(deps): update dependency sass to v1.63.3 2023-06-09 16:05:26 +00:00
dd3a5fe6b5
fix(reminders): duplicate reminder for each change 2023-06-09 14:46:34 +02:00
04642ae1ec
fix(reminders): set date over relative reminder 2023-06-09 14:43:02 +02:00
eac19e28d6
feat(reminders): add more spacing 2023-06-09 14:39:35 +02:00
11f94e4037
feat(reminders): make adding new reminders less confusing 2023-06-09 14:37:26 +02:00
39cc7a00d8
feat(reminders): make relative presets actually work 2023-06-09 14:31:32 +02:00
02da1e171e
feat(reminders): add confirm button 2023-06-09 14:27:11 +02:00
ae177c73ea
feat(reminders): move reminder settings to a popup 2023-06-09 14:23:32 +02:00
e6c4c18974
feat(reminders): translate all reminder form strings 2023-06-09 13:54:17 +02:00
95487d7569
feat(reminders): add proper time picker for relative dates 2023-06-09 13:19:47 +02:00
7b2a688b6e
feat(datepicker): separate datepicker popup and datepicker logic in different components 2023-06-09 12:07:23 +02:00
Frederick [Bot]
f5b3b21ce0 [skip ci] Updated translations via Crowdin 2023-06-09 00:29:38 +00:00
979561342a
fix(kanban): decrease task count per bucket when deleting a task 2023-06-08 17:09:48 +02:00
ad27f588a2
feat(kanban): use total task count from the api instead of manually calculating it per bucket
This fixes an ux issue where the total count would show a wrong number of total tasks because that was the number of tasks which were loaded at the time. In combination with bucket limits, this caused error messages when the user would attempt to drag tasks into a bucket which appeared not full but was.
2023-06-08 16:57:58 +02:00
c7a989d7dc
fix(kanban): don't export buckets as readonly because that makes it impossible to update them, even from within the store
This fixes a bug where the task on the kanban board would not get updated because the "tasks" property of all buckets were still read only if they were exported once as readonly. This has been unnoticed in the past because the visual representation of the board still perfectly matched what the user was doing and what was saved in the api - just not what was stored in pina.
2023-06-08 16:54:52 +02:00
0e674d8300 chore(deps): update dependency @rushstack/eslint-patch to v1.3.1 2023-06-08 06:45:00 +00:00
121fd70235 chore(deps): update dependency sass to v1.63.2 2023-06-08 02:06:22 +00:00
d4cd90da45 chore(deps): update dependency sass to v1.63.0 2023-06-07 21:05:13 +00:00
c74612f24a
fix: lint 2023-06-07 21:53:40 +02:00
64f9f4fd88
fix: disable autocomplete in assignee search
Resolves https://github.com/go-vikunja/frontend/issues/114
2023-06-07 21:46:18 +02:00
b50adaf4b5
fix(navigation): highlight saved filters in project view and prevent them from being dragged around 2023-06-07 20:58:49 +02:00
7b92028e67 chore(deps): update dependency cypress to v12.14.0 2023-06-07 17:05:24 +00:00
08d84f7994 chore(deps): update dependency rollup to v3.24.0 2023-06-07 06:51:59 +00:00
f95b138b9f fix(deps): update dependency marked to v5.0.5 2023-06-07 05:05:20 +00:00
Frederick [Bot]
e6aecbd8dc [skip ci] Updated translations via Crowdin 2023-06-07 00:29:36 +00:00
eab0600f63 chore(deps): update dependency vitest to v0.32.0 2023-06-06 18:05:23 +00:00
46f5dcb4dc
fix(navigation): show text ellipsis for very long project titles 2023-06-06 17:33:55 +02:00
0dc7e83dc4
fix(navigation): menu item overflow 2023-06-06 17:32:18 +02:00
82c10b87c8
fix(navigation): hide archived subprojects 2023-06-06 17:29:08 +02:00
5888946861
fix(navigation): sidebar top spacing 2023-06-06 17:27:05 +02:00
e24607ed3a chore(deps): update dependency @types/codemirror to v5.60.8 2023-06-06 02:05:16 +00:00
Frederick [Bot]
d1ae6a8b84 [skip ci] Updated translations via Crowdin 2023-06-06 00:29:33 +00:00
fc052cf8f5 chore(deps): update typescript-eslint monorepo to v5.59.9 2023-06-05 18:05:53 +00:00
d9f608e8b4 feat: improve user assignments via quick add magic (#3348)
Reviewed-on: vikunja/frontend#3348
2023-06-05 15:03:14 +00:00
a988565227 feat: hide quick add magic help behind a tooltip (#3353)
Reviewed-on: vikunja/frontend#3353
2023-06-05 15:02:42 +00:00
Dominik Pschenitschni
b76fffb788 Update 'src/i18n/lang/en.json' 2023-06-05 14:31:30 +00:00
25c3b7bcbf chore: move styles to components 2023-06-05 14:20:41 +00:00
dfa6cd777b fix: add more padding to the textarea 2023-06-05 14:20:41 +00:00
21ad8301f2 chore: add comment on overriding 2023-06-05 14:20:41 +00:00
7110c9a5ce chore: move positioning css 2023-06-05 14:20:41 +00:00
a4c8fccb11 chore: remove nesting 2023-06-05 14:20:41 +00:00
c294f9d28d fix: lint 2023-06-05 14:20:41 +00:00
422d7fc693 feat: highlight hint icon when hovering the input 2023-06-05 14:20:41 +00:00
abb5128426 feat: add transition to input icons 2023-06-05 14:20:41 +00:00
2174608801 fix: improve tooltip text 2023-06-05 14:20:41 +00:00
a6cdf6c4bd fix: improve tooltip icon contrast 2023-06-05 14:20:41 +00:00
2c9693a83e chore: remove wrapper div 2023-06-05 14:20:41 +00:00
6989558963 feat: move quick add magic to a popup behind an icon 2023-06-05 14:20:41 +00:00
7fb85dacec feat: allow hiding the quick add magic help tooltip with a button 2023-06-05 14:20:41 +00:00
57218d1454 fix: allow icon changes configuration via env (#3567)
`window.ALLOW_ICON_CHANGES` needs to be written as a boolean during the docker deploy instead of a string. The strings `"true"` and `"false"` both evaluate to `true` in JS, so we need to use the boolean `true` and `false` for the assertion in `Logo.vue` to be meaningful.

Co-authored-by: SteffeyDev <steffeydev@icloud.com>
Reviewed-on: vikunja/frontend#3567
Reviewed-by: konrad <k@knt.li>
Co-authored-by: steffeydev <steffeydev@icloud.com>
Co-committed-by: steffeydev <steffeydev@icloud.com>
2023-06-05 14:19:55 +00:00
9df6950d1a
feat: start adding relative reminder picker with more options 2023-06-05 16:16:10 +02:00
cd2b7fe185 fix: lint 2023-06-05 14:09:19 +00:00
52987060b1 chore: group return parameter 2023-06-05 14:09:19 +00:00
aeb73a374f chore: make fuzzy matching a paramater 2023-06-05 14:09:19 +00:00
bc416f282f fix: make type singular 2023-06-05 14:09:19 +00:00
f88c373742 chore(i18n): clarify translation string 2023-06-05 14:09:19 +00:00
10ac1ff66a chore: use startsWith for prefix matching 2023-06-05 14:09:19 +00:00
ae025e30c6 fix: clarify user search setting 2023-06-05 14:09:19 +00:00
a1dd1d6664 chore: remove user margin from the component 2023-06-05 14:09:19 +00:00
57c64bbf71 chore: remove user margin from the component 2023-06-05 14:09:19 +00:00
218a19d907 feat(quick add magic): allow fuzzy matching of assignees when the api results are unambigous 2023-06-05 14:09:19 +00:00
7b6a13dd52 fix: ensure all matched quick add magic parts are correctly removed from the task 2023-06-05 14:09:19 +00:00
4ff0c81e37 fix: lint 2023-06-05 14:09:19 +00:00
6a15489610 feat(assignees): show user avatar in search results 2023-06-05 14:09:19 +00:00
59c942af73 feat: show initial list of users when opening the assignees view 2023-06-05 14:09:19 +00:00
302ba2bec7 chore: clarify users when can still be found even if they disabled it 2023-06-05 14:09:19 +00:00
34d1e4bddd fix(quick add magic): cleanup all assignee properties 2023-06-05 14:09:19 +00:00
02c24a4814 fix(quick add magic): use the project user service to find assignees for quick add magic 2023-06-05 14:09:19 +00:00
0724776ccb fix(quick add magic): don't replace the prefix in every occurrence when it is present in the matched part 2023-06-05 14:09:19 +00:00
11979cbee0 chore(deps): update caniuse-and-related 2023-06-05 13:24:09 +00:00
2a490bf8ef chore(deps): update dependency happy-dom to v9.20.3 2023-06-05 13:23:57 +00:00
b5d3d1a7b7 chore(deps): update pnpm to v8.6.1 2023-06-05 13:04:47 +00:00
554ffe3b9d chore(deps): update dependency rollup to v3.23.1 2023-06-04 19:08:49 +00:00
0f57be107b chore(deps): update dependency vite-plugin-pwa to v0.16.3 2023-06-03 09:33:26 +00:00
269aa6b426 chore(deps): update dependency eslint to v8.42.0 2023-06-03 00:05:32 +00:00
b316b8f2ba chore(deps): update dependency vite-plugin-pwa to v0.16.1 2023-06-02 13:20:52 +00:00
cad68e269c fix(deps): update dependency dayjs to v1.11.8 2023-06-02 13:05:16 +00:00
efb3407b87
feat: allow disabling icon changes 2023-06-02 13:51:47 +02:00
6f1ff02c04 chore(deps): update dependency vitest to v0.31.4 2023-06-02 11:40:07 +00:00
93c66b0613 chore(deps): update dependency @4tw/cypress-drag-drop to v2.2.4 2023-06-02 11:39:31 +00:00
c14644a300 chore(deps): update dependency postcss-preset-env to v8.4.2 2023-06-02 11:39:20 +00:00
02d2300608 fix(deps): update sentry-javascript monorepo to v7.54.0 2023-06-02 11:38:49 +00:00
ff918608c5 chore(deps): update dependency typescript to v5.1.3 2023-06-02 11:38:39 +00:00
aa591ee2ed chore(deps): update workbox monorepo to v7 (major) (#3556)
Reviewed-on: vikunja/frontend#3556
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-06-02 11:03:36 +00:00
f4a7943680
fix: bubble changes from the editor immediately and move the delay to callers
This gives the callers more control over when to save data and show/hide additional controls based on the input text
2023-06-02 12:40:21 +02:00
68fd4698ac
fix: don't try to set a user language if none is saved 2023-06-02 11:43:42 +02:00
Frederick [Bot]
dd039f31fe [skip ci] Updated translations via Crowdin 2023-06-01 00:28:46 +00:00
6c2dc483a2
fix: redundant ) 2023-05-31 16:27:20 +02:00
811254e6a9
wip: base review
(cherry picked from commit 3bb64f078cf333f174d247dc404355f2c8437cfd)
2023-05-31 16:25:33 +02:00
85ffed4d9a
fix: review findings 2023-05-31 16:25:33 +02:00
5fb45afb12
fix: review findings 2023-05-31 16:25:33 +02:00
fb14eca634
fix: review findings 2023-05-31 16:25:32 +02:00
14e2698833
feat: edit relative reminders 2023-05-31 16:25:32 +02:00
0d6c0c8399
chore; extract code to reminder-period.vue 2023-05-31 16:25:32 +02:00
5d38b8327f
feat: allow to edit existing relative reminders 2023-05-31 16:25:32 +02:00
f747d5b2fc
feat: Use new Reminders API instead of reminder_dates 2023-05-31 16:25:30 +02:00
8a75790453
chore: remove triggered notifications as it's not supported anywhere 2023-05-31 15:21:09 +02:00
acb212ab24
feat: set the current language to the one saved by the user on login 2023-05-31 15:17:54 +02:00
4ba02ebbb6
fix: don't try to convert a null date
Resolves vikunja/frontend#3371
2023-05-31 15:07:23 +02:00
244da46e38
fix(navigation): nav item width for items without sub projects 2023-05-31 14:37:57 +02:00
f40035dc79
chore: update nix flake 2023-05-31 13:44:14 +02:00
5f71e406fc fix(deps): update dependency marked to v5.0.4 2023-05-31 05:15:46 +00:00
Frederick [Bot]
3d11a4f03a [skip ci] Updated translations via Crowdin 2023-05-31 00:30:36 +00:00
1dfd2dc4b7 fix(deps): update dependency vue-router to v4.2.2 2023-05-30 22:46:00 +00:00
e9701660d3 chore(deps): update dependency vite-plugin-pwa to v0.15.2 2023-05-30 21:04:22 +00:00
c8dbb4c7ef chore(deps): update typescript-eslint monorepo to v5.59.8 2023-05-30 20:31:05 +00:00
1241d90268 chore(deps): update workbox monorepo to v6.6.1 (#3553)
Reviewed-on: vikunja/frontend#3553
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-05-30 20:30:46 +00:00
3de5b65977 chore(deps): update dependency vitest to v0.31.2 2023-05-30 18:45:05 +00:00
4a353553c3 chore(deps): update pnpm to v8.6.0 2023-05-30 18:42:51 +00:00
1240f31c0a chore(deps): update workbox monorepo to v6.6.0 (#3548)
Reviewed-on: vikunja/frontend#3548
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-05-30 18:12:03 +00:00
01ac84ce1e
fix: don't require variant prop on loading component as it already has a default one set 2023-05-30 20:00:02 +02:00
4c969f0a42
fix: don't allow creating a new label from filter view
Resolves vikunja/frontend#1035
2023-05-30 19:54:01 +02:00
8e2c76a33e
feat: optimize print view for project views 2023-05-30 19:50:37 +02:00
b3666ec27e chore(deps): update dependency @vitejs/plugin-legacy to v4.0.4 2023-05-30 16:36:54 +00:00
2c6862c509 chore(deps): update dependency vite-plugin-pwa to v0.15.1 2023-05-30 16:36:44 +00:00
9f8c43818c chore(deps): update dependency @types/node to v18.16.16 2023-05-30 16:36:17 +00:00
0debca91c8 chore(deps): update dependency @faker-js/faker to v8.0.2 2023-05-30 16:36:08 +00:00
7b6c9fcd24 chore(deps): update dependency postcss to v8.4.24 2023-05-30 15:05:47 +00:00
55675bf41b fix(deps): update sentry-javascript monorepo to v7.53.1 2023-05-30 14:25:03 +00:00
bb24b06031 chore(deps): update dependency vite to v4.3.9 2023-05-30 14:24:37 +00:00
dbce0376d5 fix(deps): update dependency marked to v5.0.3 2023-05-30 14:24:15 +00:00
40db144a41 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.11.0 2023-05-30 11:08:12 +00:00
f7ba3bd08f
fix: increase default auto-save timeout to 5 seconds
Related discussion: https://community.vikunja.io/t/task-description-constantly-saving-loosing-content/1350
2023-05-30 12:19:14 +02:00
ac1d374191 feat: remove namespaces, make projects infinitely nestable (#3323)
Reviewed-on: vikunja/frontend#3323
2023-05-30 10:09:39 +00:00
391992effb
fix: missing await 2023-05-30 11:37:45 +02:00
2e9ade11c3
fix: missing variant prop for loading component 2023-05-30 11:05:10 +02:00
f11a8c543b
fix(tests): project archived filter checkbox selector 2023-05-30 11:00:30 +02:00
e30a4452f2
fix(tests): new project input field 2023-05-30 10:57:08 +02:00
6cc11e64ab
fix: undefined parent project when none was selected 2023-05-30 10:56:42 +02:00
7b05ed9d3d
fix: avoid crashing browser processes during tests 2023-05-30 10:42:32 +02:00
Frederick [Bot]
dba35c0107 [skip ci] Updated translations via Crowdin 2023-05-29 00:28:01 +00:00
Frederick [Bot]
bfbc874b1d [skip ci] Updated translations via Crowdin 2023-05-28 00:29:34 +00:00
dbccdb239a
chore(tests): enable experimental memory managment for cypress tests 2023-05-24 18:32:23 +02:00
f13db9268a
fix: translation string 2023-05-24 17:41:14 +02:00
ed8de7e3eb
fix: lint 2023-05-24 15:54:37 +02:00
b34118485c
feat: allow creating a new project directly as a child project from another one 2023-05-24 15:54:37 +02:00
9c3259c660
chore: don't recalculate everything 2023-05-24 15:54:37 +02:00
a3e289c06c
chore: remove type annotation for computed 2023-05-24 15:54:37 +02:00
31b7c1f217
fix: don't set the current project when setting a project 2023-05-24 15:54:37 +02:00
c30dcff451
chore: don't show selection for parent project when no projects are available 2023-05-24 15:54:37 +02:00
086f50d4fe
chore: re-add top menu spacing 2023-05-24 15:54:36 +02:00
46e825820c
fix: sort in store 2023-05-24 15:54:36 +02:00
a3e2cbeb27
feat: replace color dot with handle icon on hover 2023-05-24 15:54:36 +02:00
a342ae67de
chore: use project id type 2023-05-24 15:54:36 +02:00
e4d97e0520
chore: don't set the current project to null if it's undefined already 2023-05-24 15:54:36 +02:00
b69a05689b
chore: move duplicate project logic to composable 2023-05-24 15:54:36 +02:00
6b824a49ab
chore: redirect to new project after creating from store 2023-05-24 15:54:36 +02:00
652db56d42
chore: remove unused code 2023-05-24 15:54:36 +02:00
afaf1846ec
chore: don't wrap a computed in another computed 2023-05-24 15:54:36 +02:00
ba452ab883
fix: move parent project handling out of useProject 2023-05-24 15:54:36 +02:00
39f699a61a
fix: rename getParentProjects method to make it clear what it does 2023-05-24 15:54:36 +02:00
4ab547810c
fix: return updated project instead of the old one 2023-05-24 15:54:35 +02:00
bbaddb9406
fix: remove leftovers of childIds 2023-05-24 15:54:35 +02:00
a2cc9ddc88
fix: properly determine if there are projects 2023-05-24 15:54:35 +02:00
175e31ca62
fix: recreate project instead of editing before 2023-05-24 15:54:35 +02:00
d414b65e7d
fix: remove unnecessary fallback 2023-05-24 15:54:35 +02:00
78158bcba5
fix: remove getProjectById and replace all usages of it 2023-05-24 15:54:35 +02:00
9402344b7e
fix: add default for level 2023-05-24 15:54:35 +02:00
3eca9f6180
fix: only bind child projects data down 2023-05-24 15:54:35 +02:00
26e3d42ed5
fix: move parent project child id mutation to store 2023-05-24 15:54:35 +02:00
6e095436e9
chore: rename flag 2023-05-24 15:54:35 +02:00
1344026494
fix: move the collapsable placeholder to the button 2023-05-24 15:54:35 +02:00
1a94496801
fix: bottom margin of project header 2023-05-24 15:54:34 +02:00
48570808e5
fix: use the color bubble as handle if the project has a color 2023-05-24 15:54:34 +02:00
a7440ed296
chore: use stores directly 2023-05-24 15:54:34 +02:00
12ebefd86a
chore: move v-if 2023-05-24 15:54:34 +02:00
6c9cbaadc8
chore: set project id from the outside 2023-05-24 15:54:34 +02:00
9b10693172
chore: replace section with a div 2023-05-24 15:54:34 +02:00
db1c6d6a41
chore: move all options to component props 2023-05-24 15:54:34 +02:00
c56787443f
chore: add types for emit 2023-05-24 15:54:34 +02:00
cb218ec0c3
feat: add setting for infinite nesting 2023-05-24 15:54:34 +02:00
0dd6f82a0e
fix: use menu tag everywhere 2023-05-24 15:54:34 +02:00
225091864f
fix: collapsing child projects 2023-05-24 15:54:34 +02:00
ebd9c4702e
feat: don't use child_projects property from api 2023-05-24 15:54:33 +02:00
4ad9773022
chore: format 2023-05-24 15:54:33 +02:00
0a17df87e9
fix: don't show child projects when the project is only a favorite 2023-05-24 15:54:33 +02:00
b567146d69
chore: move more logic to ProjectsNavigationItem.vue 2023-05-24 15:54:33 +02:00
65522a57f1
chore: move ProjectsNavigationWrapper back to navigation.vue 2023-05-24 15:54:33 +02:00
1d936618fa
feat: load all projects earlier than in the navigation and use the loading state of the store 2023-05-24 15:54:33 +02:00
76814a2d3f
chore: move loading styles to variant into the component 2023-05-24 15:54:33 +02:00
4134fcbd75
chore: remove old comment 2023-05-24 15:54:33 +02:00
49fac7db1c
chore: use <menu> instead of <ul> 2023-05-24 15:54:33 +02:00
e25273df48
fix: indention 2023-05-24 15:54:33 +02:00
638f6bea24
chore: improve prop type definition 2023-05-24 15:54:33 +02:00
ddcd6a17dc
chore: only apply padding where needed 2023-05-24 15:54:32 +02:00
4e21b463df
chore: remove old todo 2023-05-24 15:54:32 +02:00
3db4e011d4
feat: move navigation item to component 2023-05-24 15:54:32 +02:00
a0d39e6081
chore: use long variable name 2023-05-24 15:54:32 +02:00
a803bc637e
chore: rename alias 2023-05-24 15:54:32 +02:00
d4e452545a
chore: remove unused class 2023-05-24 15:54:32 +02:00
9d73ac661f
fix: remove leftover suspense 2023-05-24 15:54:32 +02:00
55e912221b
chore: use klona to clone project objet 2023-05-24 15:54:32 +02:00
d85be26761
fix: passing readonly projects data to navigation 2023-05-24 15:54:32 +02:00
ac78e85e17
chore: move loader class 2023-05-24 15:54:32 +02:00
131022da42
chore: export favorite projects from store 2023-05-24 15:54:32 +02:00
336db56316
chore: remove unnecessary map 2023-05-24 15:54:32 +02:00
b5d9afd0f7
chore: export not archived root projects 2023-05-24 15:54:31 +02:00
0be83db40f
fix: show favorite on hover 2023-05-24 15:54:31 +02:00
03f4d0b8bc
fix: don't show > for top-level projects 2023-05-24 15:54:31 +02:00
ee8f80cc70
feat: allow selecting a parent project when editing a project 2023-05-24 15:54:31 +02:00
ce887c38f3
feat: allow selecting a parent project when creating a project 2023-05-24 15:54:31 +02:00
799c0be830
feat: allow selecting a parent project when duplicating a project 2023-05-24 15:54:31 +02:00
760efa854d
feat: don't handle child projects and instead only save the ids 2023-05-24 15:54:31 +02:00
26bec05174
fix: make computed side-effect free 2023-05-24 15:54:31 +02:00
c32a198a34
chore: refactor get parents project and move to projects store 2023-05-24 15:54:31 +02:00
6a8c656dbb
feat: show all parent projects in project search 2023-05-24 15:54:31 +02:00
63ba2982c9
feat: show all parent projects in task detail view 2023-05-24 15:54:30 +02:00
9d9fb959d8
fix: add await 2023-05-24 15:54:30 +02:00
8ed201c83f
fix(filters): load projects after updating a filter 2023-05-24 15:54:30 +02:00
bfb40c9166
fix(filters): load projects after deleting a filter 2023-05-24 15:54:30 +02:00
5ea450844c
fix(filters): load projects after creating a filter 2023-05-24 15:54:30 +02:00
36bec9e64f
chore(task): move toggleFavorite to store 2023-05-24 15:54:30 +02:00
a95014dc5d
feat(projects): move hasProjects check to store 2023-05-24 15:54:30 +02:00
2579c33ee1
feat: wrap projects navigation in a <Suspense> so that we can use top level await 2023-05-24 15:54:30 +02:00
6f1baa3219
chore: use long variable name 2023-05-24 15:54:30 +02:00
4dee3a90e9
chore: rename archived message key 2023-05-24 15:54:30 +02:00
326b6eda6f
fix: use correct shortcut to open projects overview 2023-05-24 15:54:30 +02:00
85e882cc59
fix: simplify sort 2023-05-24 15:54:29 +02:00
e4379f0a22
chore: export projects as array directly from projects store 2023-05-24 15:54:29 +02:00
2bb7ff1803
chore: rename prop 2023-05-24 15:54:29 +02:00
5dd6e9a077
feat(tests): add project tests derived from old namespace tests 2023-05-24 15:54:29 +02:00
f7629c28f4
fix(projects): make sure the project hierarchy is properly updated when moving projects between parents 2023-05-24 15:54:29 +02:00
be2a38b48e
feat(navigation): show favorite projects on top 2023-05-24 15:54:29 +02:00
3ba5f531bb
fix(navigation): make sure updating a project's state works for sub projects as well. 2023-05-24 15:54:29 +02:00
10f1e69bc3
fix(navigation): make marking a project as favorite work 2023-05-24 15:54:29 +02:00
fd7d90b017
fix(navigation): make sure the Favorites project shows up when marking or unmarking a task as favorite 2023-05-24 15:54:29 +02:00
d898316918
fix(navigation): favorites project 2023-05-24 15:54:29 +02:00
a6f524e7af
fix(task detail view): make project display show the task's project 2023-05-24 15:54:29 +02:00
5e65814b8c
fix: make check if projects are available work again 2023-05-24 15:54:28 +02:00
aaa9d553d0
fix: cleanup unused translation strings 2023-05-24 15:54:28 +02:00
5685890493
fix: make tests work again 2023-05-24 15:54:28 +02:00
2e336150e0
chore: cleanup namespace leftovers 2023-05-24 15:54:28 +02:00
749dcdcd70
fix(navigation): hide left ul border 2023-05-24 15:54:28 +02:00
ab94343d07
feat(navigation): make dragging a project under another project work 2023-05-24 15:54:28 +02:00
fa71cec5c8
feat(navigation): allow dragging a project out from its parent project 2023-05-24 15:54:28 +02:00
c6f3829387
feat(navigation): make dragging a project to a parent work 2023-05-24 15:54:28 +02:00
7171b63947
fix(navigation): hover state of other menu items 2023-05-24 15:54:28 +02:00
06c4c0d921
feat(navigation): add hiding child projects 2023-05-24 15:54:28 +02:00
f2ca2d850d
feat: translate inbox project title 2023-05-24 15:54:28 +02:00
638d187a24
chore: format 2023-05-24 15:54:28 +02:00
b188d40d3c
feat(navigation): correctly show child projects 2023-05-24 15:54:27 +02:00
3ad948305f
fix(navigation): make the styles work again 2023-05-24 15:54:27 +02:00
be1f1d94c9
fix(navigation): watcher 2023-05-24 15:54:27 +02:00
06e8cdb9d2
feat: rebuild main navigation so that it works recursively with projects 2023-05-24 15:54:27 +02:00
10311b79df
fix: remove namespace routes 2023-05-24 15:54:27 +02:00
ad2690b21c
fix: remove namespace store reference 2023-05-24 15:54:27 +02:00
1bd17d6e50
feat: remove all namespace leftovers 2023-05-24 15:54:27 +02:00
a5e710bfe5
fix: route to create new project 2023-05-24 15:54:27 +02:00
e1bdabc8d6
feat: move namespaces list to projects list 2023-05-24 15:54:27 +02:00
c6ef99dde2 chore(deps): update dependency cypress to v12.13.0 2023-05-24 00:04:34 +00:00
49b508a783 fix(deps): update sentry-javascript monorepo to v7.53.0 2023-05-23 15:50:03 +00:00
52128925f5 chore(deps): update typescript-eslint monorepo to v5.59.7 2023-05-23 15:49:52 +00:00
cf0c7f9d08 chore(deps): update dependency rollup to v3.23.0 2023-05-23 15:49:28 +00:00
57d5140301 chore(deps): update dependency @types/node to v18.16.14 2023-05-23 12:04:31 +00:00
dbd9106621 chore(deps): update dependency postcss-preset-env to v8.4.1 2023-05-23 11:17:17 +00:00
e4fef0e88e chore(deps): update dependency eslint to v8.41.0 2023-05-23 11:16:31 +00:00
7ef0074ecc chore(deps): update dependency caniuse-lite to v1.0.30001489 2023-05-23 11:16:09 +00:00
17c35f6d42 chore(deps): update dependency happy-dom to v9.20.1 2023-05-23 11:15:58 +00:00
3a0844adba chore(deps): update dependency @rushstack/eslint-patch to v1.3.0 2023-05-23 11:15:13 +00:00
5b5b9022e0 chore(deps): update dependency vite-plugin-pwa to v0.15.0 2023-05-23 01:09:13 +00:00
Frederick [Bot]
0b0bd7dff6 [skip ci] Updated translations via Crowdin 2023-05-23 00:29:34 +00:00
079e3782d1 chore(deps): update dependency rollup to v3.22.0 2023-05-19 10:30:44 +00:00
a0ae9ae54c chore(deps): update dependency @types/marked to v5 2023-05-19 09:06:04 +00:00
a1b9a0ec4c fix(deps): update dependency @kyvg/vue3-notification to v2.9.1 2023-05-19 08:07:44 +00:00
1fa690670d fix(deps): update dependency vue-router to v4.2.1 2023-05-19 08:06:23 +00:00
3f0a87a5ec chore(deps): update dependency vite to v4.3.8 2023-05-19 08:05:46 +00:00
caf02f78bf chore(deps): update dependency @types/marked to v4.3.1 2023-05-18 23:05:09 +00:00
9b9fd14d27 chore(deps): update dependency vitest to v0.31.1 2023-05-17 15:05:09 +00:00
7f77efbfab chore(deps): update dependency @types/node to v18.16.11 2023-05-16 20:04:54 +00:00
53967d20cc chore(deps): update dependency vite to v4.3.7 2023-05-16 17:05:08 +00:00
ef3411f39a chore(deps): update dependency @types/node to v18.16.10 2023-05-16 11:04:50 +00:00
66e63f1363 chore(deps): update typescript-eslint monorepo to v5.59.6 2023-05-16 10:46:23 +00:00
2fe21f6b28 fix(deps): update sentry-javascript monorepo to v7.52.1 2023-05-16 10:46:09 +00:00
67df372636 chore(deps): update dependency eslint-plugin-vue to v9.13.0 2023-05-16 10:45:49 +00:00
df80e9da23 chore(deps): update dependency rollup to v3.21.8 2023-05-16 08:04:56 +00:00
13ab2efd0f chore(deps): update dependency @faker-js/faker to v8.0.1 2023-05-15 17:04:44 +00:00
0ffe96cf59 chore(deps): update dependency vite to v4.3.6 2023-05-15 16:04:58 +00:00
1808d0971d fix(deps): update sentry-javascript monorepo to v7.52.0 2023-05-15 14:05:00 +00:00
ec83a28d78 chore(deps): update pnpm to v8.5.1 2023-05-15 10:33:06 +00:00
f0320b3a58 chore(deps): update dependency caniuse-lite to v1.0.30001487 2023-05-15 10:30:51 +00:00
d93a1a4f4f chore(deps): update dependency happy-dom to v9.18.3 2023-05-15 00:05:29 +00:00
a9f9ddf6b9 chore(deps): update dependency @types/node to v18.16.9 2023-05-13 15:05:04 +00:00
6a8fe35fcf chore(deps): update dependency rollup to v3.21.7 2023-05-13 14:48:00 +00:00
94661e9e09 chore(deps): update dependency vue-tsc to v1.6.5 2023-05-13 08:04:55 +00:00
318f63d098 chore(deps): update dependency esbuild to v0.17.19 2023-05-13 01:04:45 +00:00
e2c9e83c2a chore(deps): update dependency @vitejs/plugin-vue to v4.2.3 2023-05-12 11:04:45 +00:00
cd434a0e3e chore(deps): update dependency @types/node to v18.16.8 2023-05-12 07:06:52 +00:00
9f293af804 chore(deps): update dependency @vue/tsconfig to v0.4.0 2023-05-12 07:06:24 +00:00
b175e00cfe chore(deps): update dependency @tsconfig/node18 to v2.0.1 2023-05-12 05:04:41 +00:00
Frederick [Bot]
19dd82d62a [skip ci] Updated translations via Crowdin 2023-05-12 00:29:44 +00:00
b3ddc9465a chore(deps): update dependency @vitejs/plugin-vue to v4.2.2 2023-05-11 19:57:40 +00:00
6b38f17d32 fix(deps): update dependency vue-router to v4.2.0 2023-05-11 19:57:25 +00:00
86449d4912 fix(deps): update dependency marked to v5.0.2 2023-05-11 19:57:07 +00:00
145d756251 chore(deps): update dependency @faker-js/faker to v8 2023-05-11 18:05:10 +00:00
838a11a2f6 chore(deps): update dependency eslint-plugin-vue to v9.12.0 2023-05-10 14:04:39 +00:00
3bfd3210b0 chore(deps): update dependency @types/node to v18.16.7 2023-05-10 09:46:06 +00:00
e933bfa99e fix(deps): update sentry-javascript monorepo to v7.51.2 2023-05-10 09:14:43 +00:00
f6a37a54d0 fix(deps): update dependency pinia to v2.0.36 2023-05-10 09:14:33 +00:00
e00c9bb1af feat: add hotkeys for priority, delete and favorite on the TaskDetailView (#3400)
Reviewed-on: vikunja/frontend#3400
Reviewed-by: konrad <k@knt.li>
Co-authored-by: primeapple <toni.mueller.web@mailbox.org>
Co-committed-by: primeapple <toni.mueller.web@mailbox.org>
2023-05-10 09:14:07 +00:00
018707c3d5
fix(ci): disable puppeteer chrome download 2023-05-10 10:42:44 +02:00
386727f6c5 chore(deps): update dependency @types/node to v18.16.6 2023-05-10 08:09:35 +00:00
a29ce36d6c chore(deps): update typescript-eslint monorepo to v5.59.5 2023-05-10 07:05:09 +00:00
7aed16bd6f chore(deps): update dependency rollup to v3.21.6 2023-05-10 06:03:37 +00:00
b1f3ca6e59 chore(deps): update pnpm to v8.5.0 2023-05-10 06:03:09 +00:00
4c0b8a06c5 chore(deps): update dependency cypress to v12.12.0 2023-05-09 23:05:25 +00:00
60647c50ac chore(deps): update dependency caniuse-lite to v1.0.30001486 2023-05-08 07:11:25 +00:00
59eaf1849e chore(deps): update dependency happy-dom to v9.10.9 2023-05-08 00:05:41 +00:00
fb57339050 chore(deps): update dependency eslint-plugin-vue to v9.11.1 2023-05-07 17:04:47 +00:00
f9831a6ad8 fix(deps): update dependency marked to v5.0.1 2023-05-06 21:04:51 +00:00
f25c67f80a chore(deps): update dependency eslint to v8.40.0 2023-05-06 15:26:02 +00:00
d3b0b97192 chore(deps): update dependency @types/node to v18.16.5 2023-05-06 15:25:42 +00:00
fa3be219a8 fix(deps): update dependency dompurify to v3.0.3 2023-05-06 13:04:56 +00:00
c22702d911 chore(deps): update dependency @types/node to v18.16.4 2023-05-05 14:04:43 +00:00
b25c5ff547 chore(deps): update dependency vite to v4.3.5 2023-05-05 11:04:43 +00:00
2e0a097806 chore(deps): update dependency rollup to v3.21.5 2023-05-05 05:04:46 +00:00
c2083f7924 fix(deps): update sentry-javascript monorepo to v7.51.0 2023-05-04 16:05:24 +00:00
8923261e5b fix(deps): update dependency ufo to v1.1.2 2023-05-04 07:35:00 +00:00
5391df56b0 chore(deps): update dependency vue-tsc to v1.6.4 2023-05-04 01:04:54 +00:00
d2b1f5780e chore(deps): update dependency vitest to v0.31.0 2023-05-03 19:52:49 +00:00
1717e968e1 chore(deps): update dependency rollup to v3.21.4 2023-05-03 19:04:31 +00:00
2c29bb3971 chore(deps): update pnpm to v8.4.0 2023-05-02 14:04:09 +00:00
37b8218a0a chore(deps): update node.js to v20 (#3411)
Reviewed-on: vikunja/frontend#3411
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-05-02 13:35:04 +00:00
ca7bbb5b91 chore(ci): remove netlify dependency (#3459)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#3459
2023-05-02 10:10:14 +00:00
2f3c008d2b chore(deps): update dependency vite to v4.3.4 2023-05-02 08:04:40 +00:00
c2722b7c3d fix(deps): update dependency @vueuse/core to v10.1.2 2023-05-02 07:18:12 +00:00
312abd907f chore(deps): update dependency rollup to v3.21.3 2023-05-02 07:18:02 +00:00
1b73c1ed64 chore(deps): update dependency vue-tsc to v1.6.3 2023-05-02 07:17:43 +00:00
d442d6653b fix(deps): update dependency marked to v5 2023-05-02 05:05:40 +00:00
758b8d6e2b chore(deps): update typescript-eslint monorepo to v5.59.2 2023-05-01 18:04:39 +00:00
416fd2e2a7 chore(deps): update dependency @types/marked to v4.3.0 2023-05-01 16:45:51 +00:00
15a8335f1a chore(deps): update dependency vue-tsc to v1.6.2 2023-05-01 14:04:42 +00:00
c689583669 chore(deps): update dependency netlify-cli to v14.3.1 2023-05-01 10:44:11 +00:00
7a43a7acc9 chore(deps): update dependency happy-dom to v9.10.1 2023-05-01 00:04:51 +00:00
8843418161 fix(deps): update dependency date-fns to v2.30.0 2023-04-30 06:57:42 +00:00
7c1eab13ae chore(deps): update dependency rollup to v3.21.2 2023-04-30 06:04:44 +00:00
5a69036da7 fix(deps): update dependency highlight.js to v11.8.0 2023-04-29 14:04:50 +00:00
2ad3458873 chore(deps): update dependency @types/node to v18.16.3 2023-04-29 07:04:40 +00:00
eb464343e8 chore(deps): update dependency rollup to v3.21.1 2023-04-29 06:04:42 +00:00
2f18d0cbad
fix(docker): don't set nginx worker rlimit
Resolves https://community.vikunja.io/t/helm-chart-frontend-pod-does-not-start-because-of-permission-issues-in-raspberry-pie-4-k3s/1286
2023-04-28 10:31:15 +02:00
6cd463a514 chore(deps): pin dependency @tsconfig/node18 to 2.0.0 2023-04-28 08:04:35 +00:00
05b70632c5
fix: tsconfig as per https://github.com/vuejs/tsconfig#configuration-for-node-environments 2023-04-28 09:30:45 +02:00
ca9fe6ff21
fix: tsconfig as per https://github.com/vuejs/tsconfig#configuration-for-node-environments 2023-04-28 09:16:54 +02:00
e5754300de chore(deps): update dependency @vue/tsconfig to v0.3.2 2023-04-28 07:04:31 +00:00
65134048bf chore(deps): update dependency @vitejs/plugin-vue to v4.2.1 2023-04-28 06:04:35 +00:00
8339a99747 chore(deps): update dependency @types/node to v18.16.2 2023-04-28 05:17:36 +00:00
3e1ae41e70 fix(deps): update dependency axios to v1.4.0 2023-04-28 00:04:55 +00:00
f757ba3441 chore(deps): update dependency vue-tsc to v1.6.1 2023-04-27 19:04:37 +00:00
6499c9cb5b fix(deps): update sentry-javascript monorepo to v7.50.0 2023-04-27 09:39:32 +00:00
28e5440d8b fix(deps): update dependency codemirror to v5.65.13 2023-04-27 09:04:38 +00:00
fef8c4d0f4 chore(deps): update dependency vue-tsc to v1.6.0 2023-04-26 21:06:27 +00:00
99e5059c64 chore(deps): update dependency cypress to v12.11.0 2023-04-26 18:04:42 +00:00
5df4f39d95 chore(deps): update dependency vite to v4.3.3 2023-04-26 15:04:55 +00:00
7ec5a70ccb chore(deps): update dependency sass to v1.62.1 2023-04-26 10:05:07 +00:00
72fcab6e78 chore(deps): update dependency @types/node to v18.16.1 2023-04-26 09:04:23 +00:00
292c90425e
fix: lint 2023-04-26 10:19:49 +02:00
b80f070431
feat: show avatar and full name in team overview 2023-04-25 18:32:36 +02:00
03936c0403 chore(deps): update dependency vite to v4.3.2 2023-04-25 16:04:40 +00:00
62825d2e64
fix: add spacing between checkbox and title of related task
Related to https://github.com/go-vikunja/frontend/issues/111
2023-04-25 17:33:47 +02:00
5cd5caef45 chore(deps): update dependency @vue/eslint-config-typescript to v11.0.3 2023-04-25 15:17:58 +00:00
798e8b529d chore(deps): update dependency @vitejs/plugin-legacy to v4.0.3 2023-04-25 15:04:36 +00:00
0e3766c5a5 chore(deps): update typescript-eslint monorepo to v5.59.1 2023-04-25 13:05:07 +00:00
90207a4427 chore(deps): update dependency @vitejs/plugin-vue to v4.2.0 2023-04-25 10:05:12 +00:00
60993a886a chore(deps): update dependency caniuse-lite to v1.0.30001481 2023-04-24 06:16:59 +00:00
a6b42f9181 chore(deps): update dependency happy-dom to v9.9.2 2023-04-24 06:16:43 +00:00
98fbd7c53c chore(deps): update dependency netlify-cli to v14 2023-04-24 01:05:19 +00:00
8d533f50e8 chore(deps): update dependency rollup to v3.21.0 2023-04-23 20:04:46 +00:00
707459ec77 chore(deps): update dependency @types/node to v18.16.0 2023-04-23 06:04:51 +00:00
faf7db649e chore(deps): update dependency esbuild to v0.17.18 2023-04-22 21:04:36 +00:00
202e71be48 chore(deps): update dependency vue-tsc to v1.4.4 2023-04-22 20:04:41 +00:00
d6e8b418d3 chore(deps): update dependency vue-tsc to v1.4.3 2023-04-22 16:04:50 +00:00
a9f41f6114 chore(deps): update dependency eslint to v8.39.0 2023-04-22 15:45:11 +00:00
f6f0d52518 fix(deps): update dependency @vueuse/core to v10.1.0 2023-04-22 10:04:50 +00:00
ccb9be42c2 chore(deps): update dependency vue-tsc to v1.4.2 2023-04-21 11:04:30 +00:00
179009bfe3 chore(deps): update dependency @types/node to v18.15.13 2023-04-21 07:04:39 +00:00
8c2bd94a9f chore(deps): update dependency vue-tsc to v1.4.1 2023-04-21 06:13:48 +00:00
7757166d75 chore(deps): update dependency rollup to v3.20.7 2023-04-21 05:04:36 +00:00
7f03002972 chore(deps): update dependency vite to v4.3.1 2023-04-20 20:04:26 +00:00
8555006d9e chore(deps): update dependency vue-tsc to v1.4.0 2023-04-20 18:04:40 +00:00
713ad64658 fix(deps): update sentry-javascript monorepo to v7.49.0 2023-04-20 16:04:46 +00:00
0713d481e3 fix(deps): update dependency pinia to v2.0.35 2023-04-20 11:04:28 +00:00
ace0cf3588 chore(deps): update dependency vite to v4.3.0 2023-04-20 09:04:31 +00:00
bba3bbfe89 chore(deps): update dependency @types/dompurify to v3.0.2 2023-04-20 08:04:42 +00:00
754afc5496 chore(deps): update dependency @types/node to v18.15.12 2023-04-20 00:05:04 +00:00
f1e8892ab5 chore(deps): update dependency postcss to v8.4.23 2023-04-19 20:32:32 +00:00
c11e192c4e fix(deps): update dependency axios to v1.3.6 2023-04-19 20:05:18 +00:00
e9c704075d chore(deps): update pnpm to v8.3.1 2023-04-19 13:04:16 +00:00
35edcb5672 chore(deps): update dependency rollup to v3.20.6 2023-04-18 12:05:05 +00:00
4695798176 chore(deps): update dependency vite to v4.2.2 2023-04-18 10:05:15 +00:00
7a323fd170 chore(deps): update dependency rollup to v3.20.5 2023-04-18 06:04:54 +00:00
1d6e4b6e32 chore(deps): update pnpm to v8.3.0 2023-04-18 01:04:56 +00:00
5524aa7998 chore(deps): update dependency cypress to v12.10.0 2023-04-17 19:04:51 +00:00
15ff2008e3 chore(deps): update typescript-eslint monorepo to v5.59.0 2023-04-17 18:05:11 +00:00
9bc2e6e165 chore(deps): update dependency postcss-preset-env to v8.3.2 2023-04-17 07:04:46 +00:00
344001856c chore(deps): update dependency rollup to v3.20.4 2023-04-17 06:04:41 +00:00
ad261fcc2f chore(deps): update dependency esbuild to v0.17.17 2023-04-17 05:29:14 +00:00
5142a0ae72 chore(deps): update dependency caniuse-lite to v1.0.30001479 2023-04-17 05:27:04 +00:00
6d195f96c9 chore(deps): update dependency happy-dom to v9.7.1 2023-04-17 01:05:56 +00:00
Frederick [Bot]
1917b217a8 [skip ci] Updated translations via Crowdin 2023-04-17 00:25:34 +00:00
1f6b01bc73 chore(deps): update dependency rollup to v3.20.3 2023-04-16 17:19:59 +00:00
d47a16aa8e chore(deps): update dependency postcss to v8.4.22 2023-04-16 14:04:37 +00:00
c57d00a74b chore(deps): update dependency eslint-plugin-vue to v9.11.0 2023-04-15 01:04:45 +00:00
77ea7fa0ee fix(deps): update dependency @vueuse/core to v10.0.2 2023-04-14 22:04:52 +00:00
b92d780cda chore: formatting 2023-04-14 21:53:04 +00:00
f14e721caf fix: rename resolveRef 2023-04-14 21:53:04 +00:00
1ff6399112 fix(deps): update dependency @vueuse/core to v10 2023-04-14 21:53:04 +00:00
503fb8da76 fix(deps): update dependency dompurify to v3.0.2 2023-04-14 18:04:56 +00:00
f050cb7015 chore(deps): update histoire to v0.16.1 2023-04-14 11:04:40 +00:00
3670916f36 fix(deps): update sentry-javascript monorepo to v7.48.0 2023-04-14 10:04:35 +00:00
Frederick [Bot]
838a063eaa [skip ci] Updated translations via Crowdin 2023-04-14 00:26:31 +00:00
e1b16b11d6 chore(deps): update node.js to v18.16.0 2023-04-13 01:04:28 +00:00
314cbf471f feat: better vscode vitest integration 2023-04-12 15:39:49 +00:00
a416d26f7c
chore: better function naming in password components 2023-04-12 16:15:40 +02:00
795b26e1dd feat: improve datemathHelp.vue 2023-04-12 07:33:45 +00:00
14666cf9d8 chore(deps): update dependency sass to v1.62.0 2023-04-12 05:57:33 +00:00
c938f31935 chore(deps): update pnpm to v8 2023-04-11 23:05:49 +00:00
35a52ef01b
fix(quick add magic): date parsing with a date at the beginning
Resolves https://github.com/go-vikunja/frontend/issues/110
2023-04-11 18:12:08 +02:00
3b05ce3f10 chore(deps): update histoire to v0.16.0 2023-04-11 14:04:49 +00:00
aec4fd7a2d chore(deps): update dependency vitest to v0.30.1 2023-04-11 12:04:57 +00:00
2661af3a17 fix(deps): update sentry-javascript monorepo to v7.47.0 2023-04-11 12:01:30 +00:00
56f43bae3f chore(deps): update typescript-eslint monorepo to v5.58.0 2023-04-10 18:04:41 +00:00
84472d2e9c chore(deps): update dependency happy-dom to v9.1.9 2023-04-10 13:17:25 +00:00
c5afcd63b0 chore(deps): update dependency postcss-preset-env to v8.3.1 2023-04-10 12:05:14 +00:00
9bdb257814 chore(deps): update dependency caniuse-lite to v1.0.30001477 2023-04-10 11:14:22 +00:00
5ad9891b16 chore(deps): update pnpm to v7.32.0 2023-04-10 11:13:57 +00:00
7c04064917 chore(deps): update dependency vitest to v0.30.0 2023-04-10 11:13:43 +00:00
fb5383d86b chore(deps): update dependency esbuild to v0.17.16 2023-04-10 05:04:46 +00:00
68af314ec0 chore(deps): update dependency eslint to v8.38.0 2023-04-08 00:04:43 +00:00
8b1de5ce09 fix(deps): update dependency pinia to v2.0.34 2023-04-07 20:04:46 +00:00
724b6fe091 chore(deps): update dependency typescript to v5.0.4 2023-04-07 18:04:25 +00:00
6648cd30c3 chore(deps): update dependency sass to v1.61.0 2023-04-06 22:04:45 +00:00
8b90b45739
fix: make sure the unread notifications indicator is correctly positioned
Resolves vikunja/frontend#3358
2023-04-06 16:11:12 +02:00
39be67eecf fix(deps): update dependency axios to v1.3.5 2023-04-06 09:06:06 +00:00
Frederick [Bot]
750f0ddeab [skip ci] Updated translations via Crowdin 2023-04-05 00:06:16 +00:00
6a5ece2f24 chore(deps): update pnpm to v7.31.0 2023-04-04 01:04:32 +00:00
Frederick [Bot]
4ce33abfe6 [skip ci] Updated translations via Crowdin 2023-04-04 00:06:21 +00:00
5b7e1af87d chore(deps): update dependency @types/dompurify to v3.0.1 2023-04-03 20:04:51 +00:00
59c6605b14 chore(deps): update typescript-eslint monorepo to v5.57.1 2023-04-03 18:05:29 +00:00
820d598ecd fix(i18n): orderedList translationid 2023-04-03 09:48:03 +00:00
a263ec1273
fix(Expandable): spelling ⛈ 2023-04-03 11:31:39 +02:00
b68892492c chore(deps): update dependency happy-dom to v9 2023-04-03 05:18:07 +00:00
7c97695cec chore(deps): update dependency netlify-cli to v13.2.2 2023-04-03 05:17:16 +00:00
e764f34a2d chore(deps): update dependency caniuse-lite to v1.0.30001473 2023-04-03 00:05:10 +00:00
6892a28bb6 chore(deps): update dependency esbuild to v0.17.15 2023-04-01 23:04:45 +00:00
74d688b8d2 chore(deps): update dependency csstype to v3.1.2 2023-04-01 21:04:41 +00:00
ed84651046 chore(deps): update dependency postcss-preset-env to v8.3.0 2023-03-31 17:04:54 +00:00
7468ed21fa chore(deps): update dependency typescript to v5.0.3 2023-03-30 21:05:23 +00:00
d8015913c3 fix(deps): update sentry-javascript monorepo to v7.46.0 2023-03-30 14:04:41 +00:00
Frederick [Bot]
78789834f0 [skip ci] Updated translations via Crowdin 2023-03-30 00:06:17 +00:00
739fe0caa1
fix: move types to dev dependencies 2023-03-29 22:23:40 +02:00
4703f9c4d5
fix: has-pseudo-class polyfill 2023-03-29 17:22:06 +02:00
fd699ad777
fix: checkbox label size based on icon 2023-03-29 17:17:51 +02:00
0acf44778d
fix: undo further nesting of interactive items
This is really bad for UX and accessability
2023-03-29 17:17:50 +02:00
8fc254d2db
feat: abstract BaseCheckbox 2023-03-29 17:17:49 +02:00
Dominik Pschenitschni
7d3b97d422 feat: prepare for pnpm 8 (#3331)
Related: vikunja/frontend#3317

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#3331
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-03-29 12:02:34 +00:00
4a34f245db
chore(deps): update flake 2023-03-29 13:41:52 +02:00
973ea39a64 chore(deps): update dependency eslint to v8.37.0 2023-03-29 07:56:52 +00:00
f94a65ce7a chore(deps): update dependency @types/node to v18.15.11 2023-03-28 22:05:12 +00:00
432fbbea78 chore(deps): update dependency cypress to v12.9.0 2023-03-28 19:05:20 +00:00
e483f1cd2e chore(deps): update dependency vitest to v0.29.8 2023-03-28 14:05:19 +00:00
eb34f6e136 chore(deps): update dependency postcss-preset-env to v8.2.0 2023-03-28 12:05:42 +00:00
91e9eef582 fix: use strict comparison 2023-03-28 10:49:34 +00:00
dea1789a00
feat: type i18n improvements 2023-03-28 12:35:19 +02:00
30adad5ae6 feat: mark undone if task moved from isDoneBucket (#3291)
Addresses #545 (not completely)

Reviewed-on: vikunja/frontend#3291
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-28 10:21:19 +00:00
3ed6f939e5 chore(deps): update dependency vite-plugin-pwa to v0.14.7 2023-03-28 10:05:08 +00:00
a337d22c1f fix(deps): update font awesome to v6.4.0 2023-03-27 18:27:34 +00:00
addfcf2510 chore(deps): update typescript-eslint monorepo to v5.57.0 2023-03-27 18:05:13 +00:00
303034f02c chore(deps): update pnpm to v7.30.5 2023-03-27 14:04:54 +00:00
0fd44e9484 chore(deps): update dependency caniuse-lite to v1.0.30001470 2023-03-27 06:06:31 +00:00
04040f20ba chore(deps): update dependency netlify-cli to v13.2.1 2023-03-27 00:06:51 +00:00
6c999ad148 fix: ensure same protocol for configured api url (#3303)
Resolves https://github.com/go-vikunja/frontend/issues/109

Vikunja would save the api url with `http` instead of `https` when the frontend was accessed via https. This was fine in most cases when the server would redirect all requests made to http to the secure https variant. However, in newer Firefox versions (and soon, Chrome probably as well) the browser would not follow that redirect anymore. Hence, we need to make sure to only make api requests to the same protocol. Doing API requests from an https hosted fronted to an http hosted api would probably fail already anyway.

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#3303
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2023-03-26 19:18:47 +00:00
cc519e6773 chore(deps): update dependency esbuild to v0.17.14 2023-03-26 03:05:27 +00:00
f9dcae4f65 chore(deps): update dependency @types/node to v18.15.10 2023-03-25 23:05:50 +00:00
ade6c2cb18 chore(deps): update dependency postcss-preset-env to v8.1.0 2023-03-25 09:05:19 +00:00
4566b62a93 chore(deps): update dependency @types/node to v18.15.9 2023-03-25 08:05:10 +00:00
37d3ef24d2 chore(deps): update dependency @types/node to v18.15.8 2023-03-25 00:05:22 +00:00
71265769ce fix: update logo change only every hour 2023-03-24 23:30:26 +00:00
a13c16ca03 fix: use time constant 2023-03-24 23:30:26 +00:00
a33fb72ef8 fix: use onActivated 2023-03-24 23:30:26 +00:00
c5776264c0 fix: only update daytime salutation when switching to home view 2023-03-24 23:30:26 +00:00
078d8b39a9 fix(gantt): only update today value when changing to the gantt chart view 2023-03-24 23:30:26 +00:00
b77c7c2f45 fix: add interval to uses of useNow so that it uses less resources 2023-03-24 23:30:26 +00:00
e369473dd0 chore(deps): update dependency esbuild to v0.17.13 2023-03-24 19:05:04 +00:00
70501f9da1 chore(deps): update pnpm to v7.30.3 2023-03-24 14:04:24 +00:00
9bb7019b09 chore(deps): update dependency rollup to v3.20.2 2023-03-24 13:21:24 +00:00
df4fe7a644 fix(deps): update sentry-javascript monorepo to v7.45.0 2023-03-24 10:06:38 +00:00
2f009d0b27 chore(deps): update dependency @types/node to v18.15.7 2023-03-24 09:05:25 +00:00
70d7def7d7 chore(deps): update dependency sass to v1.60.0 2023-03-24 07:11:01 +00:00
0033407f96 chore(deps): update pnpm to v7.30.2 2023-03-24 02:04:25 +00:00
b10a2329ca chore(deps): update dependency @types/node to v18.15.6 2023-03-23 22:05:08 +00:00
6870db4a72 fix: list view: don't sort tasks after marking one "done" (#3285)
See https://community.vikunja.io/t/list-view-tasks-being-sorted-after-marking-one-done-throws-you-off/1257/2

Reviewed-on: vikunja/frontend#3285
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-23 20:50:17 +00:00
3643ffe0d0 fix: improve the "pop" sound a bit
Trim the (noisy) silence (especially at the start, because
that delay from making a change and playing a sound is a little
annoying), with fade-in and fade-out

Edited with Audacity

File(s) history:
7f5140bbb4
955bd73feccefe8e8ca54d946d6223b43a0836ed
2023-03-23 23:38:52 +04:00
02971f6ff9 chore(deps): update dependency vite-plugin-pwa to v0.14.6 2023-03-23 12:05:31 +00:00
7d3c34b004 chore(deps): update pnpm to v7.30.1 2023-03-23 11:48:32 +00:00
f3ea6fd4dc chore(deps): update dependency eslint-plugin-vue to v9.10.0 2023-03-23 11:05:38 +00:00
bed6b81a58 chore(deps): update dependency rollup to v3.20.1 2023-03-23 09:05:02 +00:00
f9bf9139b8 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.10.0 2023-03-22 18:05:17 +00:00
96e2c81b7e
fix: ignore ts deprecations for now
see https://github.com/vuejs/tsconfig/issues/6
2023-03-22 15:47:21 +01:00
e62c00a187 chore(deps): update dependency typescript to v5 2023-03-22 13:05:25 +00:00
611419888a chore(deps): update dependency vite-plugin-pwa to v0.14.5 2023-03-22 12:05:18 +00:00
5cc7e282bf fix(deps): update dependency marked to v4.3.0 2023-03-22 06:05:26 +00:00
de0b71103c chore(deps): update dependency @vue/test-utils to v2.3.2 2023-03-21 16:05:20 +00:00
537e9e8044 fix(deps): update sentry-javascript monorepo to v7.44.2 2023-03-21 11:05:19 +00:00
ac95c1fdc8 chore(deps): update dependency @types/node to v18.15.5 2023-03-20 22:44:25 +00:00
b36da9e4d9 chore(deps): update dependency @cypress/vite-dev-server to v5.0.5 2023-03-20 22:05:07 +00:00
e11ee3c136 chore(deps): update dependency vitest to v0.29.7 2023-03-20 21:05:16 +00:00
887719ea24 chore(deps): update dependency @cypress/vue to v5.0.5 2023-03-20 19:08:09 +00:00
14f1c3b26e fix(deps): update sentry-javascript monorepo to v7.44.1 2023-03-20 19:04:28 +00:00
2142729d38 chore(deps): update typescript-eslint monorepo to v5.56.0 2023-03-20 18:06:42 +00:00
9dcc2baae2 fix(deps): update sentry-javascript monorepo to v7.44.0 2023-03-20 16:50:06 +00:00
37c88d2974 chore(deps): update dependency vitest to v0.29.5 2023-03-20 15:05:22 +00:00
36fd0deec4 chore(deps): update dependency vitest to v0.29.4 2023-03-20 14:05:04 +00:00
4a4438d431 chore(deps): update dependency vite to v4.2.1 2023-03-20 12:05:22 +00:00
28a6745346 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.9.3 2023-03-20 10:05:21 +00:00
9ae0470879 chore(deps): update pnpm to v7.30.0 2023-03-20 08:10:09 +00:00
927aed1161 chore(deps): update dependency netlify-cli to v13.1.6 2023-03-20 08:09:18 +00:00
7f3d7a656d chore(deps): update dependency caniuse-lite to v1.0.30001468 2023-03-20 08:08:31 +00:00
040a8ce095 chore(deps): update dependency rollup to v3.20.0 2023-03-20 07:05:16 +00:00
Frederick [Bot]
8974939bf2 [skip ci] Updated translations via Crowdin 2023-03-19 00:05:55 +00:00
Dominik Pschenitschni
846de369f2 chore(parseSubtasksViaIndention): fix comment (#3259)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#3259
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-03-18 07:41:36 +00:00
Frederick [Bot]
4d865af423 [skip ci] Updated translations via Crowdin 2023-03-18 00:06:08 +00:00
62ad01fc8f chore(deps): update dependency esbuild to v0.17.12 2023-03-17 07:05:23 +00:00
da0164b97d chore(deps): update histoire to v0.15.9 2023-03-16 23:05:16 +00:00
fc8711d6d8
fix: rename list to project for parsing subtasks via indention 2023-03-16 19:25:04 +01:00
03cef1f831 chore(deps): update dependency @vitejs/plugin-vue to v4.1.0 2023-03-16 15:05:00 +00:00
ee4974a494 fix: style: "favorite" button being shown on projects without hovering 2023-03-16 14:56:32 +00:00
bfbfd6a421 chore: update JSDoc example 2023-03-16 14:56:32 +00:00
49954abbbe chore(deps): update dependency vite to v4.2.0 2023-03-16 11:06:23 +00:00
2f618512cb chore(deps): update dependency @vitejs/plugin-legacy to v4.0.2 2023-03-16 09:05:39 +00:00
Frederick [Bot]
0086ebed0d [skip ci] Updated translations via Crowdin 2023-03-16 00:06:15 +00:00
1523ed9a47 chore(deps): update dependency cypress to v12.8.1 2023-03-15 22:03:55 +00:00
79c7cbedcc chore(deps): update dependency vitest to v0.29.3 2023-03-15 21:06:10 +00:00
3e128f3966 chore(deps): update pnpm to v7.29.3 2023-03-15 11:16:41 +00:00
fb45483ffc fix(deps): update dependency vue-flatpickr-component to v11.0.3 2023-03-15 10:05:24 +00:00
a9bc7d7a38 fix(deps): update dependency @types/sortablejs to v1.15.1 2023-03-15 05:05:35 +00:00
78f032d678 chore(deps): update dependency sass to v1.59.3 2023-03-14 22:05:23 +00:00
d73b71a097 fix: SortBy type import 2023-03-14 21:46:42 +00:00
2bdc6155d7 chore(deps): update dependency cypress to v12.8.0 2023-03-14 20:34:59 +00:00
f60cebf42c
fix: force usage of @types for flexsearch instead of integrated types 2023-03-14 17:06:30 +01:00
2e4c6673d4
fix(deps): update dependency flexsearch to v0.7.31 2023-03-14 17:06:30 +01:00
6e3d64d6ef fix(tests): make sure the task is created with a bucket 2023-03-14 14:04:23 +00:00
2deb66855b fix(ci): always pull latest unstable api image for testing 2023-03-14 14:04:23 +00:00
a64c0c19e5 fix: make sure redirects to a saved view work as intended 2023-03-14 14:04:23 +00:00
24b4576c00 fix(tests): wait for request instead of fixed time 2023-03-14 14:04:23 +00:00
34ad889d90 fix(link share): redirect to list view after authenticating 2023-03-14 14:04:23 +00:00
af523cfcd7 feat: add redirect for old list routes 2023-03-14 14:04:23 +00:00
842f204123 fix: improve projectView storing and add migration 2023-03-14 14:04:23 +00:00
9162002e55 fix: spacing 2023-03-14 14:04:23 +00:00
985f998a82 fix: wording 2023-03-14 14:04:23 +00:00
b93639e14e fix: rebase readd CustomTransition 2023-03-14 14:04:23 +00:00
a4be973e29 feat: improve variable naming for ProjectCardGrid 2023-03-14 14:04:23 +00:00
060a573fe9 fix: switching to view type now 2023-03-14 14:04:23 +00:00
7c43b7385d fix comment 2023-03-14 14:04:23 +00:00
befa6f27bb feat: rename list to project everywhere
fix: project table view

fix: e2e tests

fix: typo in readme

fix: list view route

fix: don't wait until background is loaded for list to show

fix: rename component imports

fix: lint

fix: parse task text

fix: use list card grid

fix: use correct class names

fix: i18n keys

fix: load project

fix: task overview

fix: list view spacing

fix: find project

fix: setLoading when updating a project

fix: loading saved filter

fix: project store loading

fix: color picker import

fix: cypress tests

feat: migrate old list settings

chore: add const for project settings

fix: wrong projecten rename from lists

chore: rename unused variable

fix: editor list

fix: shortcut list class name

fix: pagination list class name

fix: notifications list class name

fix: list view variable name

chore: clarify comment

fix: i18n keys

fix: router imports

fix: comment

chore: remove debugging leftover

fix: remove duplicate variables

fix: change comment

fix: list view variable name

fix: list view css class name

fix: list item property name

fix: name update tasks function correctly

fix: update comment

fix: project create route

fix: list view class names

fix: list view component name

fix: result list class name

fix: animation class list name

fix: change debug log

fix: revert a few navigation changes

fix: use @ for imports of all views

fix: rename link share list class

fix: remove unused css class

fix: dynamically import project components again
2023-03-14 14:04:23 +00:00
b9d3b5c756 feat: rename files with list to project 2023-03-14 14:04:23 +00:00
ee732684bc chore(deps): update dependency @types/node to v18.15.3 2023-03-14 07:05:05 +00:00
360b530dd5 chore(deps): update dependency @types/dompurify to v3 2023-03-13 21:05:38 +00:00
713c3a1a08 chore(deps): update dependency @types/node to v18.15.2 2023-03-13 19:05:09 +00:00
81b1e4035d chore(deps): update typescript-eslint monorepo to v5.55.0 2023-03-13 18:05:20 +00:00
2cde9341d4 fix(deps): update sentry-javascript monorepo to v7.43.0 2023-03-13 16:06:32 +00:00
cdf0690da6 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.9.2 2023-03-13 10:17:16 +00:00
80335e7b95 chore(deps): update dependency netlify-cli to v13.1.2 2023-03-13 09:05:32 +00:00
dbc2de14c9 chore(deps): update dependency @types/node to v18.15.1 2023-03-13 08:14:47 +00:00
c0f711d27f chore(deps): update dependency caniuse-lite to v1.0.30001465 2023-03-13 07:04:54 +00:00
df24522490
chore: 0.20.5 release preperations 2023-03-12 10:23:07 +01:00
6cf2e574bf
fix(docker): revert unprivileged user
nginx runs the init process as root so that it can bind to port 80. All worker processes run as an unprivileged user and thus the attack surface is minimal.
The previous solution didn't change the user id of the user running Vikunja and thus didn't have an effect anyway.

Related to #3228
2023-03-11 21:56:47 +01:00
e7b89ae44f
fix(docker): add cap_net_bind to the nginx binary in the docker container 2023-03-11 21:16:31 +01:00
72a1aaa654 chore(deps): update dependency eslint to v8.36.0 2023-03-11 06:04:52 +00:00
c70c3b6080 chore(deps): update dependency sass to v1.59.2 2023-03-11 02:05:52 +00:00
401f2cdd7e
chore: 0.20.4 release preperations 2023-03-10 14:51:04 +01:00
013472e899 fix(i18n): load language files before doing anything else (#3218)
Resolves #3214

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#3218
2023-03-10 13:46:23 +00:00
e9d48c442d chore(deps): update dependency rollup to v3.19.1 2023-03-10 13:05:34 +00:00
6837038922 chore(deps): update dependency autoprefixer to v10.4.14 2023-03-10 07:05:55 +00:00
6cf7c75954 chore(deps): update dependency @types/node to v18.15.0 2023-03-10 01:06:32 +00:00
Frederick [Bot]
52d6677d93 [skip ci] Updated translations via Crowdin 2023-03-10 00:06:22 +00:00
f8f8c8ac6e chore(deps): update dependency vite-plugin-inject-preload to v1.3.1 2023-03-09 20:53:21 +00:00
97bd5d77b6 chore(deps): update dependency rollup to v3.19.0 2023-03-09 20:05:23 +00:00
5278bcbac2 fix(deps): update sentry-javascript monorepo to v7.42.0 2023-03-09 14:05:28 +00:00
194fef0dab fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.9.1 2023-03-09 11:42:49 +00:00
97b6ba06dd chore(deps): update dependency @vue/test-utils to v2.3.1 2023-03-09 05:04:25 +00:00
Frederick [Bot]
559cfde8da [skip ci] Updated translations via Crowdin 2023-03-09 00:06:07 +00:00
9db3aedde9 chore: remove an unused duplicate key
Introduced in 172d353df, feels like it was by error
2023-03-08 16:06:56 +00:00
0eb78e32f9 chore: improve @/message action type (#3209)
Reviewed-on: vikunja/frontend#3209
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-08 09:51:55 +00:00
b4dd23b85d fix: i18ze a string (#3210)
Reviewed-on: vikunja/frontend#3210
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-08 09:43:46 +00:00
2262b49aaf chore(deps): update pnpm to v7.29.1 2023-03-08 06:56:34 +00:00
c887990bad fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.9.0 2023-03-08 06:05:02 +00:00
37c5a88744 chore(deps): update node.js to v18.15.0 2023-03-07 20:03:33 +00:00
9b7770ade4 fix(keyboard-shortcuts): use card prop 2023-03-07 17:19:21 +00:00
af4a039502
chore: histoire add logo link 2023-03-07 18:07:12 +01:00
1b06112db4 fix: collapse menu on mobile when path changes 2023-03-07 15:56:09 +00:00
0952f059c0 fix(deps): update dependency pinia to v2.0.33 2023-03-07 15:08:55 +00:00
0f97ba6ec9 fix: sync sidebar transition with <main> (#3200)
Reviewed-on: vikunja/frontend#3200
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-06 18:56:13 +00:00
d4c9edb55d chore(deps): update typescript-eslint monorepo to v5.54.1 2023-03-06 18:04:27 +00:00
394f056cf4 fix(deps): update sentry-javascript monorepo to v7.41.0 2023-03-06 13:04:12 +00:00
7672676b6e chore(deps): update pnpm to v7.29.0 2023-03-06 07:30:39 +00:00
51b33fd67e chore(deps): update dependency caniuse-lite to v1.0.30001460 2023-03-06 07:30:06 +00:00
0ed3ebda94 chore(deps): update dependency netlify-cli to v13.0.1 2023-03-06 00:06:16 +00:00
danstewart
7b6f76d1b4 fix: stop revealing elements on hover if hover is not supported (#3191)
Resolves #3162

Co-authored-by: Dan Stewart <git@mail.danstewart.dev>
Reviewed-on: vikunja/frontend#3191
Reviewed-by: konrad <k@knt.li>
Co-authored-by: danstewart <danstewart@noreply.kolaente.de>
Co-committed-by: danstewart <danstewart@noreply.kolaente.de>
2023-03-04 16:13:31 +00:00
ad0029789d chore(deps): update dependency esbuild to v0.17.11 2023-03-03 23:04:25 +00:00
e13f57c30a chore(deps): update dependency @types/node to v18.14.6 2023-03-03 22:04:16 +00:00
6a3518dace chore(refactor): improve stores/config types (#3190)
Reviewed-on: vikunja/frontend#3190
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-03 14:36:59 +00:00
f1ec554d09 chore(deps): update dependency @types/node to v18.14.5 2023-03-03 06:04:12 +00:00
6aa02e29b1
chore(services): let getAll: always return Model[] 2023-03-02 16:44:01 +01:00
5f9485414b
chore(services): add examples for some functions 2023-03-02 16:43:46 +01:00
149ceaf2e5 fix(quick-actions): nothing happening on team click (#3186)
Reviewed-on: vikunja/frontend#3186
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2023-03-02 15:28:43 +00:00
9b3e185dd4 chore(deps): update dependency @types/node to v18.14.4 2023-03-02 09:04:10 +00:00
779fe3e323 fix(deps): update sentry-javascript monorepo to v7.40.0 2023-03-02 08:02:54 +00:00
a27b77f24e fix(deps): update dependency dompurify to v3.0.1 2023-03-02 08:02:13 +00:00
41f22a1035 chore(deps): update dependency rollup to v3.18.0 2023-03-01 23:04:23 +00:00
28d01c5ba0 chore(deps): update dependency vitest to v0.29.2 2023-03-01 19:04:08 +00:00
Frederick [Bot]
e272dd8e64 [skip ci] Updated translations via Crowdin 2023-03-01 00:06:13 +00:00
c002275e7f
fix(table view): correctly load sort order from local storage
Resolves https://community.vikunja.io/t/table-view-sort-by-due-date-doesnt-persist-after-page-refresh/1198
2023-02-28 11:56:05 +01:00
1392d7f101 fix(deps): update dependency ufo to v1.1.1 2023-02-27 21:43:42 +00:00
e5758e21c7 chore(deps): update typescript-eslint monorepo to v5.54.0 2023-02-27 18:04:10 +00:00
e0f06999be
feat: improve recommended vscode settings 2023-02-27 16:52:48 +01:00
3b72acff27 fix(deps): update sentry-javascript monorepo to v7.39.0 2023-02-27 15:49:34 +00:00
df1c44aabe chore(deps): update dependency @types/node to v18.14.2 2023-02-27 15:04:16 +00:00
fe764a46e9
fix(task): allow clicking on the whole task to open the task detail view
Resolves #3172
2023-02-27 16:00:08 +01:00
000e3080a5 chore(deps): update dependency start-server-and-test to v2 2023-02-27 13:05:45 +00:00
f4c568e961 chore(deps): update dependency start-server-and-test to v1.15.5 2023-02-27 08:55:40 +00:00
ef70ead3f0 chore(deps): update dependency caniuse-lite to v1.0.30001458 2023-02-27 08:55:03 +00:00
7a326d6e03 chore(deps): update dependency happy-dom to v8.9.0 2023-02-27 08:54:32 +00:00
afb6383a85 chore(deps): update dependency netlify-cli to v13 2023-02-27 01:06:23 +00:00
e49969dcad chore(deps): update dependency rollup to v3.17.3 2023-02-26 13:26:09 +00:00
81e1d70847 chore(deps): update dependency eslint to v8.35.0 2023-02-26 10:04:19 +00:00
5226517954 chore(deps): update pnpm to v7.28.0 2023-02-25 17:03:54 +00:00
d5d0f9a8e2 chore(deps): update dependency vitest to v0.29.1 2023-02-25 11:04:09 +00:00
2337b6c9f3 chore(deps): update dependency vue-tsc to v1.2.0 2023-02-25 10:18:01 +00:00
289802b13d chore(deps): update dependency cypress to v12.7.0 2023-02-25 03:04:10 +00:00
5de9a2880f chore(deps): update dependency @cypress/vite-dev-server to v5.0.4 2023-02-24 04:04:04 +00:00
Frederick [Bot]
62f6895950 [skip ci] Updated translations via Crowdin 2023-02-24 00:06:10 +00:00
c198b9a164 chore(deps): update dependency @types/node to v18.14.1 2023-02-23 12:04:39 +00:00
d5f5e2a412 fix(deps): update dependency axios to v1.3.4 2023-02-22 22:04:25 +00:00
cabee68bbb
fix(docker): make sure the service worker and webmanifest are never cached 2023-02-22 12:18:46 +01:00
2fd2214a2e
fix(menu): don't show drag handle for not draggable menu items 2023-02-22 12:17:33 +01:00
64735e0c3d
fix(filter): don't allow marking a filter as favorite
Resolves https://community.vikunja.io/t/error-favouriting-filters-lists/1161/1
2023-02-22 12:13:48 +01:00
1f40b68108
fix(filter): validate title before creating or editing a filter
Resolves #3152
2023-02-22 11:04:31 +01:00
4033c28a67 chore(deps): update dependency vue-tsc to v1.1.7 2023-02-22 04:04:05 +00:00
be20a01dd6 chore(deps): update dependency vite to v4.1.4 2023-02-21 20:04:08 +00:00
8f5a628e54 chore(deps): update node.js to v18.14.2 2023-02-21 19:03:24 +00:00
e0c00b306e fix(deps): update dependency pinia to v2.0.32 2023-02-21 08:04:09 +00:00
10eaacc552 chore(deps): update dependency vue-tsc to v1.1.5 2023-02-20 20:04:05 +00:00
4b1465955a chore(deps): update typescript-eslint monorepo to v5.53.0 2023-02-20 19:16:38 +00:00
1711318212 chore(deps): update dependency esbuild to v0.17.10 2023-02-20 18:04:33 +00:00
b042547aaa chore(deps): update dependency netlify-cli to v12.13.2 2023-02-20 12:29:07 +00:00
ed0db956eb chore(deps): update dependency happy-dom to v8.6.0 2023-02-20 12:04:07 +00:00
a66f8a6484 chore(deps): update dependency rollup to v3.17.2 2023-02-20 11:34:43 +00:00
47e895149e chore(deps): update dependency vue-tsc to v1.1.4 2023-02-20 11:34:01 +00:00
8e00014feb fix(deps): update dependency pinia to v2.0.31 2023-02-20 11:33:30 +00:00
6146340034 fix(deps): update dependency codemirror to v5.65.12 2023-02-20 11:04:16 +00:00
c7b761b0eb chore(deps): update dependency caniuse-lite to v1.0.30001457 2023-02-20 09:32:51 +00:00
a1e84b3460 chore(deps): update dependency @vue/test-utils to v2.3.0 2023-02-20 09:32:12 +00:00
038debaa22 chore(deps): update dependency vite to v4.1.3 2023-02-20 09:04:17 +00:00
88faf04251 chore(deps): update dependency esbuild to v0.17.9 2023-02-19 18:04:07 +00:00
04be2b9745 chore(deps): update dependency rollup to v3.17.1 2023-02-18 20:04:05 +00:00
815e8cce0e chore(deps): update dependency sass to v1.58.3 2023-02-18 13:04:11 +00:00
d12f9247ff chore(deps): update dependency vue-tsc to v1.1.3 2023-02-18 12:38:10 +00:00
85e7a17934 chore(deps): update pnpm to v7.27.1 2023-02-18 12:37:43 +00:00
59c5d43348 chore(deps): update dependency rollup to v3.17.0 2023-02-18 12:37:10 +00:00
c011f9aa52 fix(deps): update dependency @vueuse/core to v9.13.0 2023-02-18 12:36:29 +00:00
b9f5319a4f chore(deps): update histoire to v0.15.8 2023-02-18 12:04:22 +00:00
f120ba4169 chore(deps): update dependency @types/node to v18.14.0 2023-02-17 21:04:08 +00:00
b2b70f4a9d chore(deps): update dependency @cypress/vite-dev-server to v5.0.3 2023-02-17 15:04:01 +00:00
9facffe3e9 fix(deps): update dependency blurhash to v2.0.5 2023-02-17 14:38:30 +00:00
c31aff1d88 chore(deps): update histoire to v0.15.7 2023-02-17 14:23:27 +00:00
60dea80462 chore(deps): update dependency rollup to v3.16.0 2023-02-17 14:20:21 +00:00
cd10ccfbc0 fix(deps): update sentry-javascript monorepo to v7.38.0 2023-02-17 14:04:40 +00:00
8647402038 chore(deps): update dependency vite to v4.1.2 2023-02-17 11:04:08 +00:00
990fd46302 chore(deps): update node.js to v18.14.1 2023-02-17 10:17:25 +00:00
cf0aafd9e6 fix(deps): update dependency ufo to v1.1.0 2023-02-17 09:15:10 +00:00
70d2535e93 chore(deps): update dependency sass to v1.58.2 2023-02-17 02:04:07 +00:00
Frederick [Bot]
0c6f1a4083 [skip ci] Updated translations via Crowdin 2023-02-17 00:06:13 +00:00
29eb42932a chore(deps): update dependency vue-tsc to v1.1.2 2023-02-16 17:04:00 +00:00
736e9051d8 chore(deps): update histoire to v0.15.4 2023-02-16 10:04:01 +00:00
4a4c401558 chore(deps): update dependency cypress to v12.6.0 (#3115)
Reviewed-on: vikunja/frontend#3115
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-02-15 22:45:51 +00:00
9198abe24d chore(deps): pin node.js to 18.14.0 2023-02-15 22:02:55 +00:00
Dominik Pschenitschni
97c8970dd6 feat: use renovate js-app as preset (#3087)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#3087
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-02-15 21:58:35 +00:00
5303b6bc97 chore(deps): update dependency vue-tsc to v1.1.0 2023-02-15 20:04:24 +00:00
24a0a8f5eb chore(deps): update histoire to v0.15.3 2023-02-15 19:03:46 +00:00
Dominik Pschenitschni
d07ad495e2 fix(postcss-preset-env): client side polyfills (#3051)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#3051
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-02-15 15:40:00 +00:00
8465afe421 chore(deps): update histoire to v0.15.1 2023-02-15 15:03:47 +00:00
d40729cbe7
fix: button styles
Partially reverts eaeddda4e4
2023-02-15 11:28:25 +01:00
fa0e46a399
chore: remove sponsor 2023-02-15 11:12:00 +01:00
b78481f9f6 fix(deps): update dependency @kyvg/vue3-notification to v2.9.0 (#3113)
Reviewed-on: vikunja/frontend#3113
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-02-14 15:16:03 +00:00
cbc9cf6f7f fix(deps): update dependency vue-flatpickr-component to v11.0.2 (#3112)
Reviewed-on: vikunja/frontend#3112
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-02-14 10:28:55 +00:00
62fd9a656e chore(deps): update dependency sass to v1.58.1 2023-02-14 01:04:11 +00:00
85269b4524 chore(deps): update dependency start-server-and-test to v1.15.4 (#3109)
Reviewed-on: vikunja/frontend#3109
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-02-13 20:15:41 +00:00
536d709961 fix(deps): update dependency axios to v1.3.3 2023-02-13 19:04:37 +00:00
59d6d7e786 chore(deps): update typescript-eslint monorepo to v5.52.0 2023-02-13 18:04:07 +00:00
ae86d0d42a fix(deps): update dependency dompurify to v3 (#3107)
Reviewed-on: vikunja/frontend#3107
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-02-13 17:39:29 +00:00
9a20b7a853 fix(deps): update sentry-javascript monorepo to v7.37.2 2023-02-13 16:04:08 +00:00
5687b66ea5 chore(deps): update dependency vitest to v0.28.5 2023-02-13 13:04:04 +00:00
1da411e1f6 chore(deps): update dependency vite-plugin-inject-preload to v1.3.0 2023-02-13 09:48:27 +00:00
e8a6d3f31b chore(deps): update dependency caniuse-lite to v1.0.30001451 2023-02-13 09:47:50 +00:00
a25a795276 chore(deps): update dependency netlify-cli to v12.12.0 2023-02-13 09:45:48 +00:00
57f6abd99f chore(deps): update dependency esbuild to v0.17.8 2023-02-13 07:04:03 +00:00
84d205f90b chore(deps): update dependency vite-plugin-pwa to v0.14.4 2023-02-11 10:04:05 +00:00
de91e7c9ae chore(deps): update histoire to v0.14.2 2023-02-11 09:04:37 +00:00
2cf9c35acb chore(deps): update dependency eslint to v8.34.0 2023-02-11 08:04:56 +00:00
db525db6eb fix(deps): histoire renovate group 2023-02-11 08:04:20 +00:00
88525ae7c8 chore(deps): include histoire main package in histoire renovate group 2023-02-11 08:03:36 +00:00
957bfdc8f1 chore(deps): update dependency histoire to v0.14.2 2023-02-11 00:05:13 +00:00
c52ae83b75 fix(deps): update sentry-javascript monorepo to v7.37.1 2023-02-10 16:03:55 +00:00
df40c4e475
chore(deps): update dependency histoire to v0.14.0 2023-02-10 14:29:39 +01:00
3f41e9a3a6 chore(deps): update dependency @histoire/plugin-vue to v0.14.0 2023-02-10 13:04:04 +00:00
1da510b5dd
chore(deps): update dependency @histoire/plugin-screenshot to v0.14.0 2023-02-10 13:35:01 +01:00
536db3fd46 chore(deps): update dependency @histoire/plugin-vue to v0.14.0 2023-02-10 12:26:56 +00:00
cefa5250c5
chore(deps): create a group for all histoire dependencies 2023-02-10 13:10:46 +01:00
f697640636
chore: remove minimist dependency (not used anywhere) 2023-02-10 12:57:48 +01:00
09b7595b68 chore(deps): update dependency rollup to v3.15.0 2023-02-10 07:08:20 +00:00
6b7f73f724 chore(deps): update dependency esbuild to v0.17.7 2023-02-09 23:04:14 +00:00
Dominik Pschenitschni
d6b55c7570 feat: fix calculation of token invalidation (#3077)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#3077
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-02-09 21:45:18 +00:00
Yurii Vlasov
3f4b08b8be Added ipv6 control script 2023-02-09 21:43:32 +00:00
791c61cabb
fix(docker): default api url 2023-02-09 22:30:36 +01:00
e3dd4ef78a feat: persistent menuActive state with Local Storage (#3011)
Reviewed-on: vikunja/frontend#3011
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-02-09 21:14:49 +00:00
830d0887b9 fix(deps): update sentry-javascript monorepo to v7.37.0 2023-02-09 17:04:06 +00:00
e8db2c2b45
feat: header improvements 2023-02-09 15:19:33 +01:00
706a13242e fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.8.2 2023-02-08 23:34:16 +00:00
13fab10584 chore(deps): update dependency histoire to v0.13.2 2023-02-08 23:05:01 +00:00
4b0c8aa66b chore(deps): update dependency @histoire/plugin-vue to v0.13.2 2023-02-08 22:04:14 +00:00
bfaf9401f4 chore(deps): update dependency @histoire/plugin-screenshot to v0.13.2 2023-02-08 21:07:12 +00:00
13607124a6
chore(deps): update dependency histoire to v0.13.1 2023-02-08 17:34:13 +01:00
9fc3d0a965 chore(deps): update dependency vite-plugin-pwa to v0.14.3 2023-02-08 16:27:37 +00:00
4d6286451e chore(deps): update dependency @histoire/plugin-vue to v0.13.1 2023-02-08 16:04:15 +00:00
0479d17e69 chore(deps): update dependency @histoire/plugin-screenshot to v0.13.1 2023-02-08 15:04:13 +00:00
5ca272959d chore(deps): update pnpm to v7.27.0 2023-02-08 14:03:48 +00:00
c502f9b840
feat: refactor to composable
- using useMediaQuery and useLocalStorage
- remove watcher in contentAuth
2023-02-08 12:56:32 +01:00
a3a313a21f fix(deps): update font awesome to v6.3.0 2023-02-07 20:04:47 +00:00
c58d1ffd2e chore(deps): update dependency vite-plugin-pwa to v0.14.2 2023-02-07 17:04:16 +00:00
99dc5cf34f
Refactor to only used local storage value when on desktop viewport widths 2023-02-07 14:58:45 +01:00
3604cb3ec7
Solve for resize() 2023-02-07 14:58:45 +01:00
aa01a92278
Persist menuActive state in Local Storage 2023-02-07 14:58:44 +01:00
Dominik Pschenitschni
7b96397e3b feat: use klona instead of lodash.clonedeep (#3073)
Resolves: vikunja/frontend#3032
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#3073
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-02-07 13:04:03 +00:00
b45a4e1aaf chore(deps): update dependency @types/node to v18.13.0 2023-02-07 09:04:26 +00:00
Frederick [Bot]
d3365d6add [skip ci] Updated translations via Crowdin 2023-02-07 00:10:26 +00:00
49cb2b9e6f chore(deps): update dependency @cypress/vue to v5.0.4 2023-02-06 22:04:02 +00:00
d4ce10e79a chore(deps): update dependency esbuild to v0.17.6 2023-02-06 20:04:01 +00:00
345c5e3588 chore(deps): update typescript-eslint monorepo to v5.51.0 2023-02-06 18:04:17 +00:00
7ff84bcd29 chore(deps): update dependency happy-dom to v8.2.6 2023-02-06 10:56:28 +00:00
d1633ef622 chore(deps): update dependency @histoire/plugin-vue to v0.13.0 2023-02-06 10:04:27 +00:00
7e92bc63ac chore(deps): update caniuse-and-related 2023-02-06 09:41:54 +00:00
be076b65cf chore(deps): update dependency histoire to v0.13.0 2023-02-06 01:05:40 +00:00
Frederick [Bot]
65b90cbee0 [skip ci] Updated translations via Crowdin 2023-02-06 00:09:40 +00:00
74aac1b245 chore(deps): update dependency @histoire/plugin-screenshot to v0.13.0 2023-02-05 23:38:03 +00:00
ade791ed43 chore(deps): update dependency @types/node to v18.11.19 2023-02-05 12:04:08 +00:00
55b008c67c chore(deps): update dependency rollup to v3.14.0 2023-02-05 06:03:59 +00:00
Frederick [Bot]
1f088cca18 [skip ci] Updated translations via Crowdin 2023-02-05 00:10:26 +00:00
6fad1e4969 fix(deps): update dependency axios to v1.3.2 2023-02-03 19:04:04 +00:00
eaeddda4e4
feat: improve naming and styles 2023-02-03 17:25:38 +01:00
7cbf0acac5
fix: always show update popup on top 2023-02-03 17:04:51 +01:00
3db5ea45d7
feat: move update from navigation to app 2023-02-03 17:04:51 +01:00
RoboMagus
dcd5c3fd6a
Disable listening on IPv6 ports when IPv6 is not supported (#102) 2023-02-03 15:55:36 +01:00
61fff44764 chore(deps): update dependency rollup to v3.13.0 2023-02-03 13:03:57 +00:00
ecdae4e03e chore(deps): update dependency vitest to v0.28.4 2023-02-03 11:04:06 +00:00
b26ea45fe0
chore: update funding links 2023-02-03 11:47:46 +01:00
7cb0cd293d
chore: update funding links 2023-02-03 11:46:37 +01:00
6572f75e5d fix: Use Build Time Base Path (#2964)
Reviewed-on: vikunja/frontend#2964
Reviewed-by: konrad <k@knt.li>
2023-02-03 08:57:27 +00:00
af55992057
feat(config): Support Setting Base Path in .env
* This uses loadEnv to load an environment file at configuration
  time.
  * Documentation:
    * https://vitejs.dev/config/#environment-variables
  * More on environment files:
    * https://vitejs.dev/guide/env-and-mode.html
  * `VIKUNJA_FRONTEND_BASE` is the variable in the environment
     file that will be used to set Vite’s base option.
* This adds a commented example to .env.local.example

Signed-off-by: Jef Oliver <jef@eljef.me>
2023-02-03 09:21:08 +01:00
e92559dc00
fix(base): Use Build Time Base Path
* If a base path is provided at build time, use it.
  * Base path can be set with `VIKUNJA_FRONTEND_BASE` at
    build time
    * `VIKUNJA_FRONTEND_BASE` sets `import.meta.env.BASE_URL` after Vite resolves it.
    * Usages of `import.meta.env.BASE_URL` are statically replaced
      at build time.
    * If base path is not provided, `import.meta.env.BASE_URL`
      defaults to '/'.
    * Documentation:
      https://vitejs.dev/guide/env-and-mode.html

* Fixes:
  * Manifest not loading because of incorrect path.
  * Service Worker not loading because path is incorrect in
    manifest.
  * Service Worker crashing because import of workbox is from
    wrong path.
  * Service Worker not loading a task because path is incorrect
    in event listener.
  * Incorrect URLs being set on window because base path is
    incorrect.
    * ex: `/login` vs `/base/login`

Signed-off-by: Jef Oliver <jef@eljef.me>
2023-02-03 09:21:06 +01:00
3dbf02fd7a chore(deps): update dependency @vue/test-utils to v2.2.10 2023-02-03 00:05:11 +00:00
81a4f2d977 chore: typo 2023-02-02 19:11:08 +00:00
2972d0d400 chore(deps): update dependency cypress to v12.5.1 2023-02-02 18:03:56 +00:00
c11ebc44c4 chore(deps): update dependency vite to v4.1.1 2023-02-02 15:04:03 +00:00
144f90c5f7 fix(deps): update sentry-javascript monorepo to v7.36.0 2023-02-02 14:14:47 +00:00
913879604a chore(deps): update dependency @vitejs/plugin-legacy to v4.0.1 2023-02-02 14:03:59 +00:00
1589ed5739 chore(deps): update dependency @vitejs/plugin-legacy to v4 2023-02-02 12:04:02 +00:00
a991c537ac chore(deps): update dependency postcss-preset-env to v8 (#3000)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#3000
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-02-02 11:49:30 +00:00
69b57aa23a chore(deps): update dependency vite to v4.1.0 2023-02-02 11:04:00 +00:00
1a1939963a fix(deps): update dependency vue to v3.2.47 2023-02-02 07:03:57 +00:00
3d62c9789c fix(deps): update dependency axios to v1.3.1 2023-02-02 06:56:51 +00:00
c18df8687c chore(deps): update dependency @vue/test-utils to v2.2.9 2023-02-02 00:05:20 +00:00
d83ba0c158 fix(deps): update dependency pinia to v2.0.30 (#3042)
Reviewed-on: vikunja/frontend#3042
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-02-01 14:26:35 +00:00
cea31d1da7
fix(docker): cross compilation with buildx 2023-02-01 15:08:12 +01:00
12509a7e0f fix(deps): update sentry-javascript monorepo to v7.35.0 (#3041)
Reviewed-on: vikunja/frontend#3041
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-02-01 13:12:20 +00:00
dd43057a08 chore(deps): update dependency rollup to v3.12.1 2023-02-01 10:03:47 +00:00
19d3cf01cd chore(deps): update pnpm to v7.26.3 2023-02-01 09:36:45 +00:00
80012bf035 chore(deps): update dependency cypress to v12.5.0 2023-02-01 09:11:17 +00:00
899d9e1cb7 chore(deps): update dependency sass to v1.58.0 2023-02-01 02:04:06 +00:00
56830ddadc fix(deps): update dependency axios to v1.3.0 (#3036)
Reviewed-on: vikunja/frontend#3036
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-31 17:16:19 +00:00
1749d6ba0a
fix(list): make sure favorite lists are not duplicated in the menu when renaming them
Resolves vikunja/frontend#3031
2023-01-31 17:12:11 +01:00
b29008d304 chore(deps): update typescript-eslint monorepo to v5.50.0 2023-01-31 10:03:47 +00:00
8ae3054b1a chore(deps): update dependency typescript to v4.9.5 2023-01-30 22:03:50 +00:00
434 changed files with 38706 additions and 26723 deletions

View File

@ -15,6 +15,7 @@ trigger:
services:
- name: api
image: vikunja/api:unstable
pull: always
environment:
VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB
VIKUNJA_LOG_LEVEL: DEBUG
@ -41,11 +42,12 @@ steps:
# - .cache
- name: dependencies
image: node:18-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
CYPRESS_CACHE_FOLDER: .cache/cypress
PUPPETEER_SKIP_DOWNLOAD: true
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
- pnpm install --fetch-timeout 100000
@ -53,7 +55,7 @@ steps:
# - restore-cache
- name: lint
image: node:18-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -64,7 +66,7 @@ steps:
- dependencies
- name: build-prod
image: node:18-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -75,7 +77,7 @@ steps:
- dependencies
- name: test-unit
image: node:18-alpine
image: node:20.11.0-alpine
pull: always
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
@ -85,7 +87,7 @@ steps:
- name: typecheck
failure: ignore
image: node:18-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -135,8 +137,9 @@ steps:
# - dependencies
- name: deploy-preview
image: node:18-alpine
image: williamjackson/netlify-cli
pull: always
user: root # The rest runs as root and thus the permissions wouldn't work
environment:
NETLIFY_AUTH_TOKEN:
from_secret: netlify_auth_token
@ -199,10 +202,15 @@ steps:
# - .cache
- name: build
image: node:18-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
SENTRY_AUTH_TOKEN:
from_secret: sentry_auth_token
SENTRY_ORG: vikunja
SENTRY_PROJECT: frontend-oss
PUPPETEER_SKIP_DOWNLOAD: true
commands:
- apk add git
- corepack enable && pnpm config set store-dir .cache/pnpm
@ -218,6 +226,7 @@ steps:
image: kolaente/zip
pull: always
commands:
- cp src/version.json dist
- cd dist
- zip -r ../vikunja-frontend-unstable.zip *
- cd ..
@ -276,10 +285,14 @@ steps:
# - .cache
- name: build
image: node:18-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
SENTRY_AUTH_TOKEN:
from_secret: sentry_auth_token
SENTRY_ORG: vikunja
SENTRY_PROJECT: frontend-oss
commands:
- apk add git
- corepack enable && pnpm config set store-dir .cache/pnpm
@ -295,6 +308,7 @@ steps:
image: kolaente/zip
pull: always
commands:
- cp src/version.json dist
- cd dist
- zip -r ../vikunja-frontend-${DRONE_TAG##v}.zip *
- cd ..
@ -346,8 +360,7 @@ type: docker
name: docker-release
depends_on:
- release-latest
- release-version
- build
trigger:
ref:
@ -375,8 +388,7 @@ steps:
repo: vikunja/frontend
tags: unstable
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=unstable
- USE_RELEASE=false
platforms:
- linux/386
- linux/amd64
@ -410,8 +422,7 @@ steps:
from_secret: docker_password
repo: vikunja/frontend
build_args:
- USE_RELEASE=true
- RELEASE_VERSION=${DRONE_TAG##v}
- USE_RELEASE=false
platforms:
- linux/386
- linux/amd64
@ -464,8 +475,10 @@ name: update-translations
trigger:
branch:
include:
- main
event:
include:
- cron
cron:
- update_translations
@ -473,15 +486,14 @@ trigger:
steps:
- name: download
pull: always
image: jonasfranz/crowdin
image: ghcr.io/kolaente/kolaente/drone-crowdin-v2:latest
settings:
download: true
export_dir: src/i18n/lang/
ignore_branch: true
project_identifier: vikunja
environment:
CROWDIN_KEY:
crowdin_key:
from_secret: crowdin_key
project_id: 462614
target: download
download_to: src/i18n/lang/
download_export_approved_only: true
- name: move-files
pull: always
@ -501,26 +513,25 @@ steps:
author_name: Frederick [Bot]
branch: main
commit: true
commit_message: "[skip ci] Updated translations via Crowdin"
commit_message: "chore(i18n): update translations via Crowdin"
remote: "ssh://git@kolaente.dev:9022/vikunja/frontend.git"
ssh_key:
from_secret: translation_git_push_ssh_key
from_secret: git_push_ssh_key
- name: upload
pull: always
image: jonasfranz/crowdin
image: ghcr.io/kolaente/kolaente/drone-crowdin-v2:latest
depends_on:
- clone
settings:
files:
en.json: src/i18n/lang/en.json
ignore_branch: true
project_identifier: vikunja
environment:
CROWDIN_KEY:
crowdin_key:
from_secret: crowdin_key
project_id: 462614
target: upload
upload_files:
src/i18n/lang/en.json: en.json
---
kind: signature
hmac: 971875b90c7bb1649d1b00d022d0b594ba9b68f927bf8f0dbe840190816d676b
hmac: a044c7c4db3c2a11299d4d118397e9d25be36db241723a1bbd0a2f9cc90ffdac
...

View File

@ -1,8 +1,12 @@
# Duplicate this file and remove the '.example' suffix.
# Adjust the values as needed.
# (1) Duplicate this file and remove the '.example' suffix.
# Naming this file '.env.local' is a Vite convention to prevent accidentally
# submitting to git.
# For more info see: https://vitejs.dev/guide/env-and-mode.html#env-files
VITE_IS_ONLINE=true
VITE_WORKBOX_DEBUG=false
SENTRY_AUTH_TOKEN=YOUR_TOKEN
SENTRY_ORG=vikunja
SENTRY_PROJECT=frontend-oss
# (2) Comment in and adjust the values as needed.
# VITE_IS_ONLINE=true
# SENTRY_AUTH_TOKEN=YOUR_TOKEN
# SENTRY_ORG=vikunja
# SENTRY_PROJECT=frontend-oss
# VIKUNJA_FRONTEND_BASE=/custom-subpath

View File

@ -1,5 +1,5 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution")
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
'root': true,
@ -7,52 +7,54 @@ module.exports = {
'browser': true,
'es2022': true,
'node': true,
'vue/setup-compiler-macros': true,
},
'extends': [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:vue/vue3-recommended',
'@vue/eslint-config-typescript/recommended',
],
'rules': {
'vue/html-quotes': [
'error',
'double',
],
'quotes': [
'error',
'single',
],
'comma-dangle': [
'error',
'always-multiline',
],
'semi': [
'error',
'never',
],
'quotes': ['error', 'single'],
'comma-dangle': ['error', 'always-multiline'],
'semi': ['error', 'never'],
// see https://segmentfault.com/q/1010000040813116/a-1020000041134455 (original in chinese)
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }],
'vue/v-on-event-hyphenation': ['warn', 'never', { 'autofix': true }],
'vue/multi-word-component-names': 'off',
'vue/multi-word-component-names': 0,
// disabled until we have support for reactivityTransform
// See https://github.com/vuejs/eslint-plugin-vue/issues/1948
// see also setting in `vite.config`
'vue/no-setup-props-destructure': 0,
// uncategorized rules:
'vue/component-api-style': ['error', ['script-setup']],
'vue/component-name-in-template-casing': ['warn', 'PascalCase'],
'vue/custom-event-name-casing': ['error', 'camelCase'],
'vue/define-macros-order': 'error',
'vue/match-component-file-name': ['error', {
'extensions': ['.js', '.jsx', '.ts', '.tsx', '.vue'],
'shouldMatchCase': true,
}],
'vue/no-boolean-default': ['warn', 'default-false'],
'vue/match-component-import-name': 'error',
'vue/prefer-separate-static-class': 'warn',
'vue/padding-line-between-blocks': 'error',
'vue/next-tick-style': ['error', 'promise'],
'vue/block-lang': [
'error',
{ 'script': { 'lang': 'ts' } },
],
'vue/no-required-prop-with-default': ['error', { 'autofix': true }],
'vue/no-duplicate-attr-inheritance': 'error',
'vue/no-empty-component-block': 'error',
'vue/html-indent': ['error', 'tab'],
// vue3
'vue/no-ref-object-destructure': 'error',
},
'parser': 'vue-eslint-parser',
'parserOptions': {
'parser': '@typescript-eslint/parser',
'ecmaVersion': 2022,
'sourceType': 'module',
'ecmaVersion': 'latest',
},
'ignorePatterns': [
'*.test.*',
'cypress/*',
],
'globals': {
'defineProps': 'readonly',
},
}

3
.github/FUNDING.yml vendored
View File

@ -1,2 +1,3 @@
github: kolaente
custom: https://www.buymeacoffee.com/kolaente
open_collective: vikunja
custom: ["https://vikunja.cloud", "https://www.buymeacoffee.com/kolaente"]

23
.github/workflows/lockdown.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: 'Repo Lockdown'
on:
pull_request_target:
types: opened
permissions:
issues: write
pull-requests: write
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/repo-lockdown@v4
with:
pr-comment: 'Hi! Thank you for your contribution.
This repo is only a mirror and unfortunately we can''t accept PRs made here. Please re-submit your changes to [our Gitea instance](https://kolaente.dev/vikunja/frontend/pulls).
Also check out the [contribution guidelines](https://vikunja.io/docs/development/#pull-requests).
Thank you for your understanding.'

14
.npmrc
View File

@ -1,2 +1,14 @@
auto-install-peers=true
fetch-timeout=100000
# pnpm settings
# The following settings prepare for the new default value of pnpm 8
# they can be removed directly after having moved to pnpm 8
auto-install-peers=true
dedupe-peer-dependents=true
resolve-peers-from-workspace-root=true
save-workspace-protocol=rolling
resolution-mode=lowest-direct
publishConfig.linkDirectory=true
# remove some time after having moved to pnpm 8
use-lockfile-v6=true

2
.nvmrc
View File

@ -1 +1 @@
v18
20.11.0

View File

@ -8,6 +8,7 @@
"lokalise.i18n-ally",
"mgmcdermott.vscode-language-babel",
"mikestead.dotenv",
"Syler.sass-indented"
"Syler.sass-indented",
"zixuanchen.vitest-explorer"
]
}

View File

@ -18,6 +18,12 @@
"javascriptreact",
"vue"
],
"volar.completion.preferredTagNameCase": "pascal",
// disable vetur in case it is installed
"vetur.validation.template": false,
// i18n ally
"i18n-ally.localesPaths": [
"src/i18n/lang"

File diff suppressed because it is too large Load Diff

View File

@ -3,16 +3,18 @@
# │─││ │││ │ │
# ┘─┘┘─┘┘┘─┘┘─┘
FROM node:18-alpine AS builder
FROM --platform=$BUILDPLATFORM node:20.11.0-alpine AS builder
WORKDIR /build
ARG USE_RELEASE=false
ARG RELEASE_VERSION=main
ARG RELEASE_VERSION=unstable
ENV PNPM_CACHE_FOLDER .cache/pnpm/
ENV PUPPETEER_SKIP_DOWNLOAD true
COPY package.json ./
COPY pnpm-lock.yaml ./
COPY patches ./patches/
RUN if [ "$USE_RELEASE" != true ]; then \
# https://pnpm.io/installation#using-corepack
@ -51,11 +53,15 @@ LABEL maintainer="maintainers@vikunja.io"
ENV VIKUNJA_HTTP_PORT 80
ENV VIKUNJA_HTTP2_PORT 81
ENV VIKUNJA_LOG_FORMAT main
ENV VIKUNJA_API_URL http://localhost:3456/api/v1
ENV VIKUNJA_API_URL /api/v1
ENV VIKUNJA_SENTRY_ENABLED false
ENV VIKUNJA_SENTRY_DSN https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480
ENV VIKUNJA_PROJECT_INFINITE_NESTING_ENABLED false
ENV VIKUNJA_ALLOW_ICON_CHANGES true
ENV VIKUNJA_CUSTOM_LOGO_URL "''"
COPY docker/injector.sh /docker-entrypoint.d/50-injector.sh
COPY docker/ipv6-disable.sh /docker-entrypoint.d/60-ipv6-disable.sh
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/templates/. /etc/nginx/templates/
# copy compiled files from stage 1
@ -63,6 +69,5 @@ COPY --from=builder /build/dist ./
# manage permissions
RUN chmod 0755 /docker-entrypoint.d/*.sh /etc/nginx/templates && \
chmod -R 0644 /etc/nginx/nginx.conf && \
chown -R nginx:nginx ./ /etc/nginx/conf.d /etc/nginx/templates
# unprivileged user
USER nginx
chown -R nginx:nginx ./ /etc/nginx/conf.d /etc/nginx/templates && \
rm -f /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh

View File

@ -1,10 +1,14 @@
# This repository was merged with the api and is now archived
You can find the new (old) code over on [vikunja/vikunja](https://kolaente.dev/vikunja/vikunja).
# Web frontend for Vikunja
> The todo app to organize your life.
[![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.20.3-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.22.1-brightgreen.svg)](https://dl.vikunja.io)
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
This is the web frontend for Vikunja, written in Vue.js.
@ -25,7 +29,7 @@ export DOCKER_BUILDKIT=1
docker build -t vikunja/frontend .
```
Refer to Refer [to multi-platform documentation](https://docs.docker.com/build/building/multi-platform/) in order to build for the different platform.
Refer to [multi-platform documentation](https://docs.docker.com/build/building/multi-platform/) in order to build for different platforms.
## Project setup
@ -50,7 +54,3 @@ pnpm run build
```shell
pnpm run lint
```
## Sponsors
[![Relm](https://vikunja.io/images/sponsors/relm.png)](https://relm.us)

View File

View File

@ -24,4 +24,5 @@ export default defineConfig({
},
viewportWidth: 1600,
viewportHeight: 900,
experimentalMemoryManagement: true,
})

View File

@ -1,57 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ListFactory} from '../../factories/list'
import {prepareLists} from './prepareLists'
describe('List History', () => {
createFakeUserAndLogin()
prepareLists()
it('should show a list history on the home page', () => {
cy.intercept(Cypress.env('API_URL') + '/namespaces*').as('loadNamespaces')
cy.intercept(Cypress.env('API_URL') + '/lists/*').as('loadList')
const lists = ListFactory.create(6)
cy.visit('/')
cy.wait('@loadNamespaces')
cy.get('body')
.should('not.contain', 'Last viewed')
cy.visit(`/lists/${lists[0].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[1].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[2].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[3].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[4].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
cy.visit(`/lists/${lists[5].id}`)
cy.wait('@loadNamespaces')
cy.wait('@loadList')
// cy.visit('/')
// cy.wait('@loadNamespaces')
// Not using cy.visit here to work around the redirect issue fixed in #1337
cy.get('nav.menu.top-menu a')
.contains('Overview')
.click()
cy.get('body')
.should('contain', 'Last viewed')
cy.get('[data-cy="listCardGrid"]')
.should('not.contain', lists[0].title)
.should('contain', lists[1].title)
.should('contain', lists[2].title)
.should('contain', lists[3].title)
.should('contain', lists[4].title)
.should('contain', lists[5].title)
})
})

View File

@ -1,122 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
describe('Lists', () => {
createFakeUserAndLogin()
let lists
prepareLists((newLists) => (lists = newLists))
it('Should create a new list', () => {
cy.visit('/')
cy.get('.namespace-title .dropdown-trigger')
.click()
cy.get('.namespace-title .dropdown .dropdown-item')
.contains('New list')
.click()
cy.url()
.should('contain', '/lists/new/1')
cy.get('.card-header-title')
.contains('New list')
cy.get('input.input')
.type('New List')
cy.get('.button')
.contains('Create')
.click()
cy.get('.global-notification', { timeout: 1000 }) // Waiting until the request to create the new list is done
.should('contain', 'Success')
cy.url()
.should('contain', '/lists/')
cy.get('.list-title h1')
.should('contain', 'New List')
})
it('Should redirect to a specific list view after visited', () => {
cy.visit('/lists/1/kanban')
cy.url()
.should('contain', '/lists/1/kanban')
cy.visit('/lists/1')
cy.url()
.should('contain', '/lists/1/kanban')
})
it('Should rename the list in all places', () => {
TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
const newListName = 'New list name'
cy.visit('/lists/1')
cy.get('.list-title h1')
.should('contain', 'First List')
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .menu-list-dropdown-trigger')
.click()
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-content')
.contains('Edit')
.click()
cy.get('#title')
.type(`{selectall}${newListName}`)
cy.get('footer.card-footer .button')
.contains('Save')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.list-title h1')
.should('contain', newListName)
.should('not.contain', lists[0].title)
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child')
.should('contain', newListName)
.should('not.contain', lists[0].title)
cy.visit('/')
cy.get('.card-content')
.should('contain', newListName)
.should('not.contain', lists[0].title)
})
it('Should remove a list', () => {
cy.visit(`/lists/${lists[0].id}`)
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .menu-list-dropdown-trigger')
.click()
cy.get('.namespace-container .menu.namespaces-lists .menu-list li:first-child .dropdown .dropdown-content')
.contains('Delete')
.click()
cy.url()
.should('contain', '/settings/delete')
cy.get('[data-cy="modalPrimary"]')
.contains('Do it')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.namespace-container .menu.namespaces-lists .menu-list')
.should('not.contain', lists[0].title)
cy.location('pathname')
.should('equal', '/')
})
it('Should archive a list', () => {
cy.visit(`/lists/${lists[0].id}`)
cy.get('.list-title .dropdown')
.click()
cy.get('.list-title .dropdown .dropdown-menu .dropdown-item')
.contains('Archive')
.click()
cy.get('.modal-content')
.should('contain.text', 'Archive this list')
cy.get('.modal-content [data-cy=modalPrimary]')
.click()
cy.get('.namespace-container .menu.namespaces-lists .menu-list')
.should('not.contain', lists[0].title)
cy.get('main.app-content')
.should('contain.text', 'This list is archived. It is not possible to create new or edit tasks for it.')
})
})

View File

@ -1,145 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ListFactory} from '../../factories/list'
import {NamespaceFactory} from '../../factories/namespace'
describe('Namepaces', () => {
createFakeUserAndLogin()
let namespaces
beforeEach(() => {
namespaces = NamespaceFactory.create(1)
ListFactory.create(1)
})
it('Should be all there', () => {
cy.visit('/namespaces')
cy.get('[data-cy="namespace-title"]')
.should('contain', namespaces[0].title)
})
it('Should create a new Namespace', () => {
const newNamespaceTitle = 'New Namespace'
cy.visit('/namespaces')
cy.get('[data-cy="new-namespace"]')
.should('contain', 'New namespace')
.click()
cy.url()
.should('contain', '/namespaces/new')
cy.get('.card-header-title')
.should('contain', 'New namespace')
cy.get('input.input')
.type(newNamespaceTitle)
cy.get('.button')
.contains('Create')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.namespace-container')
.should('contain', newNamespaceTitle)
cy.url()
.should('contain', '/namespaces')
})
it('Should rename the namespace all places', () => {
const newNamespaces = NamespaceFactory.create(5)
const newNamespaceName = 'New namespace name'
cy.visit('/namespaces')
cy.get(`.namespace-container .menu.namespaces-lists .namespace-title:contains(${newNamespaces[0].title}) .dropdown .dropdown-trigger`)
.click()
cy.get('.namespace-container .menu.namespaces-lists .namespace-title .dropdown .dropdown-content')
.contains('Edit')
.click()
cy.url()
.should('contain', '/settings/edit')
cy.get('#namespacetext')
.invoke('val')
.should('equal', newNamespaces[0].title) // wait until the namespace data is loaded
cy.get('#namespacetext')
.type(`{selectall}${newNamespaceName}`)
cy.get('footer.card-footer .button')
.contains('Save')
.click()
cy.get('.global-notification', { timeout: 1000 })
.should('contain', 'Success')
cy.get('.namespace-container .menu.namespaces-lists')
.should('contain', newNamespaceName)
.should('not.contain', newNamespaces[0].title)
cy.get('[data-cy="namespaces-list"]')
.should('contain', newNamespaceName)
.should('not.contain', newNamespaces[0].title)
})
it('Should remove a namespace when deleting it', () => {
const newNamespaces = NamespaceFactory.create(5)
cy.visit('/')
cy.get(`.namespace-container .menu.namespaces-lists .namespace-title:contains(${newNamespaces[0].title}) .dropdown .dropdown-trigger`)
.click()
cy.get('.namespace-container .menu.namespaces-lists .namespace-title .dropdown .dropdown-content')
.contains('Delete')
.click()
cy.url()
.should('contain', '/settings/delete')
cy.get('[data-cy="modalPrimary"]')
.contains('Do it')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.namespace-container .menu.namespaces-lists')
.should('not.contain', newNamespaces[0].title)
})
it('Should not show archived lists & namespaces if the filter is not checked', () => {
const n = NamespaceFactory.create(1, {
id: 2,
is_archived: true,
}, false)
ListFactory.create(1, {
id: 2,
namespace_id: n[0].id,
}, false)
ListFactory.create(1, {
id: 3,
is_archived: true,
}, false)
// Initial
cy.visit('/namespaces')
cy.get('.namespace')
.should('not.contain', 'Archived')
// Show archived
cy.get('[data-cy="show-archived-check"] label.check span')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
.should('be.checked')
cy.get('.namespace')
.should('contain', 'Archived')
// Don't show archived
cy.get('[data-cy="show-archived-check"] label.check span')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
// Second time visiting after unchecking
cy.visit('/namespaces')
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
cy.get('.namespace')
.should('not.contain', 'Archived')
})
})

View File

@ -1,19 +0,0 @@
import {ListFactory} from '../../factories/list'
import {NamespaceFactory} from '../../factories/namespace'
import {TaskFactory} from '../../factories/task'
export function createLists() {
NamespaceFactory.create(1)
const lists = ListFactory.create(1, {
title: 'First List'
})
TaskFactory.truncate()
return lists
}
export function prepareLists(setLists = () => {}) {
beforeEach(() => {
const lists = createLists()
setLists(lists)
})
}

View File

@ -1,39 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {ListFactory} from '../../factories/list'
import {NamespaceFactory} from '../../factories/namespace'
import {UserListFactory} from '../../factories/users_list'
describe('Editor', () => {
createFakeUserAndLogin()
beforeEach(() => {
NamespaceFactory.create(1)
ListFactory.create(1)
TaskFactory.truncate()
UserListFactory.truncate()
})
it('Has a preview with checkable checkboxes', () => {
const tasks = TaskFactory.create(1, {
description: `# Test Heading
* Bullet 1
* Bullet 2
* [ ] Checklist
* [x] Checklist checked
`,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('input[type=checkbox][data-checkbox-num=0]')
.click()
cy.get('.task-view .details.content.description h3 span.is-small.has-text-success')
.contains('Saved!')
.should('exist')
cy.get('.preview.content')
.should('contain', 'Test Heading')
})
})

View File

@ -8,20 +8,20 @@ describe('The Menu', () => {
})
it('Is visible by default on desktop', () => {
cy.get('.namespace-container')
cy.get('.menu-container')
.should('have.class', 'is-active')
})
it('Can be hidden on desktop', () => {
cy.get('button.menu-show-button:visible')
.click()
cy.get('.namespace-container')
cy.get('.menu-container')
.should('not.have.class', 'is-active')
})
it('Is hidden by default on mobile', () => {
cy.viewport('iphone-8')
cy.get('.namespace-container')
cy.get('.menu-container')
.should('not.have.class', 'is-active')
})
@ -29,7 +29,7 @@ describe('The Menu', () => {
cy.viewport('iphone-8')
cy.get('button.menu-show-button:visible')
.click()
cy.get('.namespace-container')
cy.get('.menu-container')
.should('have.class', 'is-active')
})
})

View File

@ -0,0 +1,17 @@
import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
export function createProjects() {
const projects = ProjectFactory.create(1, {
title: 'First Project'
})
TaskFactory.truncate()
return projects
}
export function prepareProjects(setProjects = (...args: any[]) => {}) {
beforeEach(() => {
const projects = createProjects()
setProjects(projects)
})
}

View File

@ -0,0 +1,50 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ProjectFactory} from '../../factories/project'
import {prepareProjects} from './prepareProjects'
describe('Project History', () => {
createFakeUserAndLogin()
prepareProjects()
it('should show a project history on the home page', () => {
cy.intercept(Cypress.env('API_URL') + '/projects*').as('loadProjectArray')
cy.intercept(Cypress.env('API_URL') + '/projects/*').as('loadProject')
const projects = ProjectFactory.create(6)
cy.visit('/')
cy.wait('@loadProjectArray')
cy.get('body')
.should('not.contain', 'Last viewed')
cy.visit(`/projects/${projects[0].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[1].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[2].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[3].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[4].id}`)
cy.wait('@loadProject')
cy.visit(`/projects/${projects[5].id}`)
cy.wait('@loadProject')
// cy.visit('/')
// Not using cy.visit here to work around the redirect issue fixed in #1337
cy.get('nav.menu.top-menu a')
.contains('Overview')
.click()
cy.get('body')
.should('contain', 'Last viewed')
cy.get('[data-cy="projectCardGrid"]')
.should('not.contain', projects[0].title)
.should('contain', projects[1].title)
.should('contain', projects[2].title)
.should('contain', projects[3].title)
.should('contain', projects[4].title)
.should('contain', projects[5].title)
})
})

View File

@ -3,15 +3,15 @@ import {formatISO, format} from 'date-fns'
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import {prepareProjects} from './prepareProjects'
describe('List View Gantt', () => {
describe('Project View Gantt', () => {
createFakeUserAndLogin()
prepareLists()
prepareProjects()
it('Hides tasks with no dates', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.g-gantt-rows-container')
.should('not.contain', tasks[0].title)
@ -25,7 +25,7 @@ describe('List View Gantt', () => {
nextMonth.setDate(1)
nextMonth.setMonth(9)
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.g-timeunits-container')
.should('contain', format(now, 'MMMM'))
@ -38,7 +38,7 @@ describe('List View Gantt', () => {
start_date: now.toISOString(),
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
})
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.g-gantt-rows-container')
.should('not.be.empty')
@ -50,7 +50,7 @@ describe('List View Gantt', () => {
start_date: null,
end_date: null,
})
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.gantt-options .fancycheckbox')
.contains('Show tasks which don\'t have dates set')
@ -69,7 +69,7 @@ describe('List View Gantt', () => {
start_date: now.toISOString(),
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
})
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.g-gantt-rows-container .g-gantt-row .g-gantt-row-bars-container div .g-gantt-bar')
.first()
@ -83,9 +83,9 @@ describe('List View Gantt', () => {
const now = Date.UTC(2022, 10, 9)
cy.clock(now, ['Date'])
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.list-gantt .gantt-options .field .control input.input.form-control')
cy.get('.project-gantt .gantt-options .field .control input.input.form-control')
.click()
cy.get('.flatpickr-calendar .flatpickr-innerContainer .dayContainer .flatpickr-day')
.first()
@ -99,13 +99,13 @@ describe('List View Gantt', () => {
})
it('Should change the date range based on date query parameters', () => {
cy.visit('/lists/1/gantt?dateFrom=2022-09-25&dateTo=2022-11-05')
cy.visit('/projects/1/gantt?dateFrom=2022-09-25&dateTo=2022-11-05')
cy.get('.g-timeunits-container')
.should('contain', 'September 2022')
.should('contain', 'October 2022')
.should('contain', 'November 2022')
cy.get('.list-gantt .gantt-options .field .control input.input.form-control')
cy.get('.project-gantt .gantt-options .field .control input.input.form-control')
.should('have.value', '25 Sep 2022 to 5 Nov 2022')
})
@ -115,7 +115,7 @@ describe('List View Gantt', () => {
start_date: formatISO(now),
end_date: formatISO(now.setDate(now.getDate() + 4)),
})
cy.visit('/lists/1/gantt')
cy.visit('/projects/1/gantt')
cy.get('.gantt-container .g-gantt-chart .g-gantt-row-bars-container .g-gantt-bar')
.dblclick()

View File

@ -1,13 +1,26 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {BucketFactory} from '../../factories/bucket'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import {prepareProjects} from './prepareProjects'
describe('List View Kanban', () => {
function createSingleTaskInBucket(count = 1, attrs = {}) {
const projects = ProjectFactory.create(1)
const buckets = BucketFactory.create(2, {
project_id: projects[0].id,
})
const tasks = TaskFactory.create(count, {
project_id: projects[0].id,
bucket_id: buckets[0].id,
...attrs,
})
return tasks[0]
}
describe('Project View Kanban', () => {
createFakeUserAndLogin()
prepareLists()
prepareProjects()
let buckets
beforeEach(() => {
@ -16,10 +29,10 @@ describe('List View Kanban', () => {
it('Shows all buckets with their tasks', () => {
const data = TaskFactory.create(10, {
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
@ -34,10 +47,10 @@ describe('List View Kanban', () => {
it('Can add a new task to a bucket', () => {
TaskFactory.create(2, {
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket')
.contains(buckets[0].title)
@ -55,7 +68,7 @@ describe('List View Kanban', () => {
})
it('Can create a new bucket', () => {
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket.new-bucket .button')
.click()
@ -69,7 +82,7 @@ describe('List View Kanban', () => {
})
it('Can set a bucket limit', () => {
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
@ -90,7 +103,7 @@ describe('List View Kanban', () => {
})
it('Can rename a bucket', () => {
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .bucket-header .title')
.first()
@ -101,7 +114,7 @@ describe('List View Kanban', () => {
})
it('Can delete a bucket', () => {
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
@ -125,10 +138,10 @@ describe('List View Kanban', () => {
it('Can drag tasks around', () => {
const tasks = TaskFactory.create(2, {
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
@ -144,10 +157,10 @@ describe('List View Kanban', () => {
it('Should navigate to the task when the task card is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
@ -158,18 +171,18 @@ describe('List View Kanban', () => {
.should('contain', `/tasks/${tasks[0].id}`, { timeout: 1000 })
})
it('Should remove a task from the kanban board when moving it to another list', () => {
const lists = ListFactory.create(2)
it('Should remove a task from the kanban board when moving it to another project', () => {
const projects = ProjectFactory.create(2)
BucketFactory.create(2, {
list_id: '{increment}',
project_id: '{increment}',
})
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
bucket_id: 1,
})
const task = tasks[0]
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(task.title)
@ -180,7 +193,7 @@ describe('List View Kanban', () => {
.contains('Move')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${lists[1].title}{enter}`)
.type(`${projects[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
@ -197,26 +210,18 @@ describe('List View Kanban', () => {
it('Shows a button to filter the kanban board', () => {
const data = TaskFactory.create(10, {
list_id: 1,
project_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.visit('/projects/1/kanban')
cy.get('.list-kanban .filter-container .base-button')
cy.get('.project-kanban .filter-container .base-button')
.should('exist')
})
it('Should remove a task from the board when deleting it', () => {
const lists = ListFactory.create(1)
const buckets = BucketFactory.create(2, {
list_id: lists[0].id,
})
const tasks = TaskFactory.create(5, {
list_id: 1,
bucket_id: buckets[0].id,
})
const task = tasks[0]
cy.visit('/lists/1/kanban')
const task = createSingleTaskInBucket(5)
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(task.title)
@ -238,4 +243,43 @@ describe('List View Kanban', () => {
cy.get('.kanban .bucket .tasks')
.should('not.contain', task.title)
})
it('Should show a task description icon if the task has a description', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
const task = createSingleTaskInBucket(1, {
description: 'Lorem Ipsum',
})
cy.visit(`/projects/${task.project_id}/kanban`)
cy.wait('@loadTasks')
cy.get('.bucket .tasks .task .footer .icon svg')
.should('exist')
})
it('Should not show a task description icon if the task has an empty description', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
const task = createSingleTaskInBucket(1, {
description: '',
})
cy.visit(`/projects/${task.project_id}/kanban`)
cy.wait('@loadTasks')
cy.get('.bucket .tasks .task .footer .icon svg')
.should('not.exist')
})
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/buckets**').as('loadTasks')
const task = createSingleTaskInBucket(1, {
description: '<p></p>',
})
cy.visit(`/projects/${task.project_id}/kanban`)
cy.wait('@loadTasks')
cy.get('.bucket .tasks .task .footer .icon svg')
.should('not.exist')
})
})

View File

@ -1,32 +1,32 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {UserListFactory} from '../../factories/users_list'
import {UserProjectFactory} from '../../factories/users_project'
import {TaskFactory} from '../../factories/task'
import {UserFactory} from '../../factories/user'
import {ListFactory} from '../../factories/list'
import {prepareLists} from './prepareLists'
import {ProjectFactory} from '../../factories/project'
import {prepareProjects} from './prepareProjects'
describe('List View List', () => {
describe('Project View Project', () => {
createFakeUserAndLogin()
prepareLists()
prepareProjects()
it('Should be an empty list', () => {
cy.visit('/lists/1')
it('Should be an empty project', () => {
cy.visit('/projects/1')
cy.url()
.should('contain', '/lists/1/list')
cy.get('.list-title h1')
.should('contain', 'First List')
cy.get('.list-title .dropdown')
.should('contain', '/projects/1/list')
cy.get('.project-title')
.should('contain', 'First Project')
cy.get('.project-title-dropdown')
.should('exist')
cy.get('p')
.contains('This list is currently empty.')
.contains('This project is currently empty.')
.should('exist')
})
it('Should create a new task', () => {
const newTaskTitle = 'New task'
cy.visit('/lists/1')
cy.visit('/projects/1')
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.get('.tasks')
@ -36,9 +36,9 @@ describe('List View List', () => {
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
})
cy.visit('/lists/1/list')
cy.visit('/projects/1/list')
cy.get('.tasks .task .tasktext')
.contains(tasks[0].title)
@ -49,33 +49,32 @@ describe('List View List', () => {
.should('contain', `/tasks/${tasks[0].id}`)
})
it('Should not see any elements for a list which is shared read only', () => {
it('Should not see any elements for a project which is shared read only', () => {
UserFactory.create(2)
UserListFactory.create(1, {
list_id: 2,
UserProjectFactory.create(1, {
project_id: 2,
user_id: 1,
right: 0,
})
const lists = ListFactory.create(2, {
const projects = ProjectFactory.create(2, {
owner_id: '{increment}',
namespace_id: '{increment}',
})
cy.visit(`/lists/${lists[1].id}/`)
cy.visit(`/projects/${projects[1].id}/`)
cy.get('.list-title .icon')
cy.get('.project-title-wrapper .icon')
.should('not.exist')
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
})
it('Should only show the color of a list in the navigation and not in the list view', () => {
const lists = ListFactory.create(1, {
it('Should only show the color of a project in the navigation and not in the list view', () => {
const projects = ProjectFactory.create(1, {
hex_color: '00db60',
})
TaskFactory.create(10, {
list_id: lists[0].id,
project_id: projects[0].id,
})
cy.visit(`/lists/${lists[0].id}/`)
cy.visit(`/projects/${projects[0].id}/`)
cy.get('.menu-list li .list-menu-link .color-bubble')
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
@ -87,9 +86,9 @@ describe('List View List', () => {
const tasks = TaskFactory.create(100, {
id: '{increment}',
title: i => `task${i}`,
list_id: 1,
project_id: 1,
})
cy.visit('/lists/1/list')
cy.visit('/projects/1/list')
cy.get('.tasks')
.should('contain', tasks[1].title)

View File

@ -2,37 +2,37 @@ import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
describe('List View Table', () => {
describe('Project View Table', () => {
createFakeUserAndLogin()
it('Should show a table with tasks', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/table')
cy.visit('/projects/1/table')
cy.get('.list-table table.table')
cy.get('.project-table table.table')
.should('exist')
cy.get('.list-table table.table')
cy.get('.project-table table.table')
.should('contain', tasks[0].title)
})
it('Should have working column switches', () => {
TaskFactory.create(1)
cy.visit('/lists/1/table')
cy.visit('/projects/1/table')
cy.get('.list-table .filter-container .items .button')
cy.get('.project-table .filter-container .items .button')
.contains('Columns')
.click()
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
.contains('Priority')
.click()
cy.get('.list-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
.contains('Done')
.click()
cy.get('.list-table table.table th')
cy.get('.project-table table.table th')
.contains('Priority')
.should('exist')
cy.get('.list-table table.table th')
cy.get('.project-table table.table th')
.contains('Done')
.should('not.exist')
})
@ -40,11 +40,11 @@ describe('List View Table', () => {
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
project_id: 1,
})
cy.visit('/lists/1/table')
cy.visit('/projects/1/table')
cy.get('.list-table table.table')
cy.get('.project-table table.table')
.contains(tasks[0].title)
.click()

View File

@ -0,0 +1,171 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {ProjectFactory} from '../../factories/project'
import {prepareProjects} from './prepareProjects'
describe('Projects', () => {
createFakeUserAndLogin()
let projects
prepareProjects((newProjects) => (projects = newProjects))
it('Should create a new project', () => {
cy.visit('/projects')
cy.get('.project-header [data-cy=new-project]')
.click()
cy.url()
.should('contain', '/projects/new')
cy.get('.card-header-title')
.contains('New project')
cy.get('input[name=projectTitle]')
.type('New Project')
cy.get('.button')
.contains('Create')
.click()
cy.get('.global-notification', {timeout: 1000}) // Waiting until the request to create the new project is done
.should('contain', 'Success')
cy.url()
.should('contain', '/projects/')
cy.get('.project-title')
.should('contain', 'New Project')
})
it('Should redirect to a specific project view after visited', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/*/buckets*').as('loadBuckets')
cy.visit('/projects/1/kanban')
cy.url()
.should('contain', '/projects/1/kanban')
cy.wait('@loadBuckets')
cy.visit('/projects/1')
cy.url()
.should('contain', '/projects/1/kanban')
})
it('Should rename the project in all places', () => {
TaskFactory.create(5, {
id: '{increment}',
project_id: 1,
})
const newProjectName = 'New project name'
cy.visit('/projects/1')
cy.get('.project-title')
.should('contain', 'First Project')
cy.get('.menu-container .menu-list li:first-child .dropdown .menu-list-dropdown-trigger')
.click()
cy.get('.menu-container .menu-list li:first-child .dropdown .dropdown-content')
.contains('Edit')
.click()
cy.get('#title')
.type(`{selectall}${newProjectName}`)
cy.get('footer.card-footer .button')
.contains('Save')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.project-title')
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
cy.get('.menu-container .menu-list li:first-child')
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
cy.visit('/')
cy.get('.project-grid')
.should('contain', newProjectName)
.should('not.contain', projects[0].title)
})
it('Should remove a project when deleting it', () => {
cy.visit(`/projects/${projects[0].id}`)
cy.get('.menu-container .menu-list li:first-child .dropdown .menu-list-dropdown-trigger')
.click()
cy.get('.menu-container .menu-list li:first-child .dropdown .dropdown-content')
.contains('Delete')
.click()
cy.url()
.should('contain', '/settings/delete')
cy.get('[data-cy="modalPrimary"]')
.contains('Do it')
.click()
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.menu-container .menu-list')
.should('not.contain', projects[0].title)
cy.location('pathname')
.should('equal', '/')
})
it('Should archive a project', () => {
cy.visit(`/projects/${projects[0].id}`)
cy.get('.project-title-dropdown')
.click()
cy.get('.project-title-dropdown .dropdown-menu .dropdown-item')
.contains('Archive')
.click()
cy.get('.modal-content')
.should('contain.text', 'Archive this project')
cy.get('.modal-content [data-cy=modalPrimary]')
.click()
cy.get('.menu-container .menu-list')
.should('not.contain', projects[0].title)
cy.get('main.app-content')
.should('contain.text', 'This project is archived. It is not possible to create new or edit tasks for it.')
})
it('Should show all projects on the projects page', () => {
const projects = ProjectFactory.create(10)
cy.visit('/projects')
projects.forEach(p => {
cy.get('[data-cy="projects-list"]')
.should('contain', p.title)
})
})
it('Should not show archived projects if the filter is not checked', () => {
ProjectFactory.create(1, {
id: 2,
}, false)
ProjectFactory.create(1, {
id: 3,
is_archived: true,
}, false)
// Initial
cy.visit('/projects')
cy.get('.project-grid')
.should('not.contain', 'Archived')
// Show archived
cy.get('[data-cy="show-archived-check"] label span')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
.should('be.checked')
cy.get('.project-grid')
.should('contain', 'Archived')
// Don't show archived
cy.get('[data-cy="show-archived-check"] label span')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
// Second time visiting after unchecking
cy.visit('/projects')
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
cy.get('.project-grid')
.should('not.contain', 'Archived')
})
})

View File

@ -1,25 +1,59 @@
import {LinkShareFactory} from '../../factories/link_sharing'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
describe('Link shares', () => {
it('Can view a link share', () => {
const lists = ListFactory.create(1)
function prepareLinkShare() {
const projects = ProjectFactory.create(1)
const tasks = TaskFactory.create(10, {
list_id: lists[0].id
project_id: projects[0].id
})
const linkShares = LinkShareFactory.create(1, {
list_id: lists[0].id,
project_id: projects[0].id,
right: 0,
})
cy.visit(`/share/${linkShares[0].hash}/auth`)
return {
share: linkShares[0],
project: projects[0],
tasks,
}
}
describe('Link shares', () => {
it('Can view a link share', () => {
const {share, project, tasks} = prepareLinkShare()
cy.visit(`/share/${share.hash}/auth`)
cy.get('h1.title')
.should('contain', lists[0].title)
.should('contain', project.title)
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
cy.get('.tasks')
.should('contain', tasks[0].title)
cy.url().should('contain', `/projects/${project.id}/list#share-auth-token=${share.hash}`)
})
it('Should work when directly viewing a project with share hash present', () => {
const {share, project, tasks} = prepareLinkShare()
cy.visit(`/projects/${project.id}/list#share-auth-token=${share.hash}`)
cy.get('h1.title')
.should('contain', project.title)
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
cy.get('.tasks')
.should('contain', tasks[0].title)
})
it('Should work when directly viewing a task with share hash present', () => {
const {share, project, tasks} = prepareLinkShare()
cy.visit(`/tasks/${tasks[0].id}#share-auth-token=${share.hash}`)
cy.get('h1.title')
.should('contain', tasks[0].title)
})
})

View File

@ -1,17 +1,15 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {seed} from '../../support/seed'
import {TaskFactory} from '../../factories/task'
import {NamespaceFactory} from '../../factories/namespace'
import {BucketFactory} from '../../factories/bucket'
import {updateUserSettings} from '../../support/updateUserSettings'
function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
NamespaceFactory.create(1)
const list = ListFactory.create()[0]
const project = ProjectFactory.create()[0]
BucketFactory.create(1, {
list_id: list.id,
project_id: project.id,
})
const tasks = []
let dueDate = startDueDate
@ -20,7 +18,7 @@ function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
dueDate = new Date(new Date(dueDate).setDate(dueDate.getDate() + 2))
tasks.push({
id: i + 1,
list_id: list.id,
project_id: project.id,
done: false,
created_by_id: 1,
title: 'Test Task ' + i,
@ -31,7 +29,7 @@ function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
})
}
seed(TaskFactory.table, tasks)
return {tasks, list}
return {tasks, project}
}
describe('Home Page Task Overview', () => {
@ -73,7 +71,7 @@ describe('Home Page Task Overview', () => {
due_date: new Date().toISOString(),
}, false)
cy.visit(`/lists/${tasks[0].list_id}/list`)
cy.visit(`/projects/${tasks[0].project_id}/list`)
cy.get('.tasks .task')
.first()
.should('contain.text', newTaskTitle)
@ -90,7 +88,7 @@ describe('Home Page Task Overview', () => {
cy.visit('/')
cy.visit(`/lists/${tasks[0].list_id}/list`)
cy.visit(`/projects/${tasks[0].project_id}/list`)
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.visit('/')
@ -113,10 +111,10 @@ describe('Home Page Task Overview', () => {
.should('contain.text', newTaskTitle)
})
it('Should show a task without a due date added via default list at the bottom', () => {
const {list} = seedTasks(40)
it('Should show a task without a due date added via default project at the bottom', () => {
const {project} = seedTasks(40)
updateUserSettings({
default_list_id: list.id,
default_project_id: project.id,
overdue_tasks_reminders_time: '9:00',
})
@ -131,23 +129,22 @@ describe('Home Page Task Overview', () => {
.should('contain.text', newTaskTitle)
})
it('Should show the cta buttons for new list when there are no tasks', () => {
it('Should show the cta buttons for new project when there are no tasks', () => {
TaskFactory.truncate()
cy.visit('/')
cy.get('.home.app-content .content')
.should('contain.text', 'You can create a new list for your new tasks:')
.should('contain.text', 'Or import your lists and tasks from other services into Vikunja:')
.should('contain.text', 'Import your projects and tasks from other services into Vikunja:')
})
it('Should not show the cta buttons for new list when there are tasks', () => {
it('Should not show the cta buttons for new project when there are tasks', () => {
seedTasks()
cy.visit('/')
cy.get('.home.app-content .content')
.should('not.contain.text', 'You can create a new list for your new tasks:')
.should('not.contain.text', 'Or import your lists and tasks from other services into Vikunja:')
.should('not.contain.text', 'You can create a new project for your new tasks:')
.should('not.contain.text', 'Or import your projects and tasks from other services into Vikunja:')
})
})

View File

@ -1,17 +1,17 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {ListFactory} from '../../factories/list'
import {ProjectFactory} from '../../factories/project'
import {TaskCommentFactory} from '../../factories/task_comment'
import {UserFactory} from '../../factories/user'
import {NamespaceFactory} from '../../factories/namespace'
import {UserListFactory} from '../../factories/users_list'
import {UserProjectFactory} from '../../factories/users_project'
import {TaskAssigneeFactory} from '../../factories/task_assignee'
import {LabelFactory} from '../../factories/labels'
import {LabelTaskFactory} from '../../factories/label_task'
import {BucketFactory} from '../../factories/bucket'
import {TaskAttachmentFactory} from '../../factories/task_attachments'
import {TaskReminderFactory} from '../../factories/task_reminders'
function addLabelToTaskAndVerify(labelTitle: string) {
cy.get('.task-view .action-buttons .button')
@ -24,7 +24,7 @@ function addLabelToTaskAndVerify(labelTitle: string) {
.first()
.click()
cy.get('.global-notification', { timeout: 4000 })
cy.get('.global-notification', {timeout: 4000})
.should('contain', 'Success')
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
.should('exist')
@ -36,7 +36,7 @@ function uploadAttachmentAndVerify(taskId: number) {
cy.get('.task-view .action-buttons .button')
.contains('Add Attachments')
.click()
cy.get('input[type=file]', {timeout: 1000})
cy.get('input[type=file]#files', {timeout: 1000})
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
cy.wait('@uploadAttachment')
@ -47,23 +47,21 @@ function uploadAttachmentAndVerify(taskId: number) {
describe('Task', () => {
createFakeUserAndLogin()
let namespaces
let lists
let projects
let buckets
beforeEach(() => {
// UserFactory.create(1)
namespaces = NamespaceFactory.create(1)
lists = ListFactory.create(1)
projects = ProjectFactory.create(1)
buckets = BucketFactory.create(1, {
list_id: lists[0].id,
project_id: projects[0].id,
})
TaskFactory.truncate()
UserListFactory.truncate()
UserProjectFactory.truncate()
})
it('Should be created new', () => {
cy.visit('/lists/1/list')
cy.visit('/projects/1/list')
cy.get('.input[placeholder="Add a new task…"')
.type('New Task')
cy.get('.button')
@ -74,11 +72,11 @@ describe('Task', () => {
.should('contain', 'New Task')
})
it('Inserts new tasks at the top of the list', () => {
it('Inserts new tasks at the top of the project', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.get('.list-is-empty-notice')
cy.visit('/projects/1/list')
cy.get('.project-is-empty-notice')
.should('not.exist')
cy.get('.input[placeholder="Add a new task…"')
.type('New Task')
@ -95,8 +93,8 @@ describe('Task', () => {
it('Marks a task as done', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.get('.tasks .task .fancycheckbox label.check')
cy.visit('/projects/1/list')
cy.get('.tasks .task .fancycheckbox')
.first()
.click()
cy.get('.global-notification')
@ -106,25 +104,65 @@ describe('Task', () => {
it('Can add a task to favorites', () => {
TaskFactory.create(1)
cy.visit('/lists/1/list')
cy.visit('/projects/1/list')
cy.get('.tasks .task .favorite')
.first()
.click()
cy.get('.menu.namespaces-lists')
cy.get('.menu-container')
.should('contain', 'Favorites')
})
it('Should show a task description icon if the task has a description', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
TaskFactory.create(1, {
description: 'Lorem Ipsum',
})
cy.visit('/projects/1/list')
cy.wait('@loadTasks')
cy.get('.tasks .task .project-task-icon')
.should('exist')
})
it('Should not show a task description icon if the task has an empty description', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
TaskFactory.create(1, {
description: '',
})
cy.visit('/projects/1/list')
cy.wait('@loadTasks')
cy.get('.tasks .task .project-task-icon')
.should('not.exist')
})
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
TaskFactory.create(1, {
description: '<p></p>',
})
cy.visit('/projects/1/list')
cy.wait('@loadTasks')
cy.get('.tasks .task .project-task-icon')
.should('not.exist')
})
describe('Task Detail View', () => {
beforeEach(() => {
TaskCommentFactory.truncate()
LabelTaskFactory.truncate()
TaskAttachmentFactory.truncate()
})
it('Shows all task details', () => {
const tasks = TaskFactory.create(1, {
id: 1,
index: 1,
description: 'Lorem ipsum dolor sit amet.'
description: 'Lorem ipsum dolor sit amet.',
})
cy.visit(`/tasks/${tasks[0].id}`)
@ -133,8 +171,7 @@ describe('Task', () => {
cy.get('.task-view h1.title.task-id')
.should('contain', '#1')
cy.get('.task-view h6.subtitle')
.should('contain', namespaces[0].title)
.should('contain', lists[0].title)
.should('contain', projects[0].title)
cy.get('.task-view .details.content.description')
.should('contain', tasks[0].description)
cy.get('.task-view .action-buttons p.created')
@ -146,7 +183,7 @@ describe('Task', () => {
id: 1,
index: 1,
done: true,
done_at: new Date().toISOString()
done_at: new Date().toISOString(),
})
cy.visit(`/tasks/${tasks[0].id}`)
@ -179,33 +216,33 @@ describe('Task', () => {
.should('contain', 'Mark as undone')
})
it('Shows a task identifier since the list has one', () => {
const lists = ListFactory.create(1, {
it('Shows a task identifier since the project has one', () => {
const projects = ProjectFactory.create(1, {
id: 1,
identifier: 'TEST',
})
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
project_id: projects[0].id,
index: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view h1.title.task-id')
.should('contain', `${lists[0].identifier}-${tasks[0].index}`)
.should('contain', `${projects[0].identifier}-${tasks[0].index}`)
})
it('Can edit the description', () => {
const tasks = TaskFactory.create(1, {
id: 1,
description: 'Lorem ipsum dolor sit amet.'
description: 'Lorem ipsum dolor sit amet.',
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .details.content.description .editor button')
cy.get('.task-view .details.content.description .tiptap button.done-edit')
.click()
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
cy.get('.task-view .details.content.description .tiptap__editor .tiptap.ProseMirror')
.type('{selectall}New Description')
cy.get('[data-cy="saveEditor"]')
.contains('Save')
@ -216,13 +253,52 @@ describe('Task', () => {
.should('exist')
})
it('Shows an empty editor when the description of a task is empty', () => {
const tasks = TaskFactory.create(1, {
id: 1,
description: '',
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
.should('have.attr', 'data-placeholder')
cy.get('.task-view .details.content.description .tiptap button.done-edit')
.should('not.exist')
})
it('Shows a preview editor when the description of a task is not empty', () => {
const tasks = TaskFactory.create(1, {
id: 1,
description: 'Lorem Ipsum dolor sit amet',
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
.should('not.have.attr', 'data-placeholder')
cy.get('.task-view .details.content.description .tiptap button.done-edit')
.should('exist')
})
it('Shows a preview editor when the description of a task contains html', () => {
const tasks = TaskFactory.create(1, {
id: 1,
description: '<p>Lorem Ipsum dolor sit amet</p>',
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
.should('not.have.attr', 'data-placeholder')
cy.get('.task-view .details.content.description .tiptap button.done-edit')
.should('exist')
})
it('Can add a new comment', () => {
const tasks = TaskFactory.create(1, {
id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .comments .media.comment .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
cy.get('.task-view .comments .media.comment .tiptap__editor .tiptap.ProseMirror')
.should('be.visible')
.type('{selectall}New Comment')
cy.get('.task-view .comments .media.comment .button:not([disabled])')
@ -230,20 +306,20 @@ describe('Task', () => {
.should('be.visible')
.click()
cy.get('.task-view .comments .media.comment .editor')
cy.get('.task-view .comments .media.comment .tiptap__editor')
.should('contain', 'New Comment')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can move a task to another list', () => {
const lists = ListFactory.create(2)
it('Can move a task to another project', () => {
const projects = ProjectFactory.create(2)
BucketFactory.create(2, {
list_id: '{increment}'
project_id: '{increment}',
})
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
project_id: projects[0].id,
})
cy.visit(`/tasks/${tasks[0].id}`)
@ -251,7 +327,7 @@ describe('Task', () => {
.contains('Move')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${lists[1].title}{enter}`)
.type(`${projects[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
@ -260,8 +336,7 @@ describe('Task', () => {
.click()
cy.get('.task-view h6.subtitle')
.should('contain', namespaces[0].title)
.should('contain', lists[1].title)
.should('contain', projects[1].title)
cy.get('.global-notification')
.should('contain', 'Success')
})
@ -269,7 +344,7 @@ describe('Task', () => {
it('Can delete a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
cy.visit(`/tasks/${tasks[0].id}`)
@ -286,17 +361,17 @@ describe('Task', () => {
cy.get('.global-notification')
.should('contain', 'Success')
cy.url()
.should('contain', `/lists/${tasks[0].list_id}/`)
.should('contain', `/projects/${tasks[0].project_id}/`)
})
it('Can add an assignee to a task', () => {
const users = UserFactory.create(5)
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
UserListFactory.create(5, {
list_id: 1,
UserProjectFactory.create(5, {
project_id: 1,
user_id: '{increment}',
})
@ -321,10 +396,10 @@ describe('Task', () => {
const users = UserFactory.create(2)
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
UserListFactory.create(5, {
list_id: 1,
UserProjectFactory.create(5, {
project_id: 1,
user_id: '{increment}',
})
TaskAssigneeFactory.create(1, {
@ -347,7 +422,7 @@ describe('Task', () => {
it('Can add a new label to a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
LabelFactory.truncate()
const newLabelText = 'some new label'
@ -375,7 +450,7 @@ describe('Task', () => {
it('Can add an existing label to a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
@ -388,13 +463,13 @@ describe('Task', () => {
it('Can add a label to a task and it shows up on the kanban board afterwards', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
project_id: projects[0].id,
bucket_id: buckets[0].id,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/lists/${lists[0].id}/kanban`)
cy.visit(`/projects/${projects[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)
@ -412,7 +487,7 @@ describe('Task', () => {
it('Can remove a label from a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
list_id: 1,
project_id: 1,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.create(1, {
@ -466,6 +541,234 @@ describe('Task', () => {
.should('contain', 'Success')
})
it('Can set a due date to a specific date for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Due Date')
.click()
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker .show')
.click()
cy.get('.datepicker-popup .flatpickr-innerContainer .flatpickr-days .flatpickr-day.today')
.click()
cy.get('[data-cy="closeDatepicker"]')
.contains('Confirm')
.click()
const today = new Date()
const day = today.toLocaleString('default', {day: 'numeric'})
const month = today.toLocaleString('default', {month: 'short'})
const year = today.toLocaleString('default', {year: 'numeric'})
const date = `${day} ${month} ${year}, 12:00:00`
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker-popup')
.should('not.exist')
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input')
.should('contain.text', date)
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can change a due date to a specific date for a task', () => {
const dueDate = new Date()
dueDate.setHours(12)
dueDate.setMinutes(0)
dueDate.setSeconds(0)
dueDate.setDate(1)
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
due_date: dueDate.toISOString(),
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Due Date')
.click()
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker .show')
.click()
cy.get('.datepicker-popup .flatpickr-innerContainer .flatpickr-days .flatpickr-day.today')
.click()
cy.get('[data-cy="closeDatepicker"]')
.contains('Confirm')
.click()
const today = new Date()
const day = today.toLocaleString('default', {day: 'numeric'})
const month = today.toLocaleString('default', {month: 'short'})
const year = today.toLocaleString('default', {year: 'numeric'})
const date = `${day} ${month} ${year}, 12:00:00`
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input .datepicker-popup')
.should('not.exist')
cy.get('.task-view .columns.details .column')
.contains('Due Date')
.get('.date-input')
.should('contain.text', date)
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can set a reminder', () => {
TaskReminderFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.click()
cy.get('.datepicker__quick-select-date')
.contains('Tomorrow')
.click()
cy.get('.reminder-options-popup')
.should('not.be.visible')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Allows to set a relative reminder when the task already has a due date', () => {
TaskReminderFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
due_date: (new Date()).toISOString(),
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.click()
cy.get('.datepicker__quick-select-date')
.should('not.exist')
cy.get('.reminder-options-popup .card-content')
.should('contain', '1 day before Due Date')
cy.get('.reminder-options-popup .card-content')
.contains('1 day before Due Date')
.click()
cy.get('.reminder-options-popup')
.should('not.be.visible')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Allows to set a relative reminder when the task already has a start date', () => {
TaskReminderFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
start_date: (new Date()).toISOString(),
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.click()
cy.get('.datepicker__quick-select-date')
.should('not.exist')
cy.get('.reminder-options-popup .card-content')
.should('contain', '1 day before Start Date')
cy.get('.reminder-options-popup .card-content')
.contains('1 day before Start Date')
.click()
cy.get('.reminder-options-popup')
.should('not.be.visible')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Allows to set a custom relative reminder when the task already has a due date', () => {
TaskReminderFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
due_date: (new Date()).toISOString(),
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.click()
cy.get('.datepicker__quick-select-date')
.should('not.exist')
cy.get('.reminder-options-popup .card-content')
.contains('Custom')
.click()
cy.get('.reminder-options-popup .card-content .reminder-period input')
.first()
.type('{selectall}10')
cy.get('.reminder-options-popup .card-content .reminder-period select')
.first()
.select('days')
cy.get('.reminder-options-popup .card-content button')
.contains('Confirm')
.click()
cy.get('.reminder-options-popup')
.should('not.be.visible')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Allows to set a fixed reminder when the task already has a due date', () => {
TaskReminderFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
done: false,
due_date: (new Date()).toISOString(),
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Set Reminders')
.click()
cy.get('.task-view .columns.details .column button')
.contains('Add a new reminder')
.click()
cy.get('.datepicker__quick-select-date')
.should('not.exist')
cy.get('.reminder-options-popup .card-content')
.contains('Date and time')
.click()
cy.get('.datepicker__quick-select-date')
.contains('Tomorrow')
.click()
cy.get('.reminder-options-popup')
.should('not.be.visible')
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can set a priority for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
@ -527,13 +830,13 @@ describe('Task', () => {
TaskAttachmentFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
list_id: lists[0].id,
project_id: projects[0].id,
bucket_id: buckets[0].id,
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/lists/${lists[0].id}/kanban`)
cy.visit(`/projects/${projects[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)
@ -552,30 +855,114 @@ describe('Task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
description: `
This is a checklist:
* [ ] one item
* [ ] another item
* [ ] third item
* [ ] fourth item
* [x] and this one is already done
`,
<ul data-type="taskList">
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
<div><p>First Item</p></div>
</li>
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
<div><p>Second Item</p></div>
</li>
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
<div><p>Third Item</p></div>
</li>
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
<div><p>Fourth Item</p></div>
</li>
<li data-checked="true" data-type="taskItem"><label><input type="checkbox"><span></span></label>
<div><p>Fifth Item</p></div>
</li>
</ul>`,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .checklist-summary')
.should('contain.text', '1 of 5 tasks')
cy.get('.editor .content ul > li input[type=checkbox]')
cy.get('.tiptap__editor ul > li input[type=checkbox]')
.eq(2)
.click()
cy.get('.editor .content ul > li input[type=checkbox]')
cy.get('.task-view .details.content.description h3 span.is-small.has-text-success')
.contains('Saved!')
.should('exist')
cy.get('.tiptap__editor ul > li input[type=checkbox]')
.eq(2)
.should('be.checked')
cy.get('.editor .content input[type=checkbox]')
cy.get('.tiptap__editor input[type=checkbox]')
.should('have.length', 5)
cy.get('.task-view .checklist-summary')
.should('contain.text', '2 of 5 tasks')
})
it('Should use the editor to render description', () => {
const tasks = TaskFactory.create(1, {
id: 1,
description: `
<h1>Lorem Ipsum</h1>
<p>Dolor sit amet</p>
<ul data-type="taskList">
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
<div><p>First Item</p></div>
</li>
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
<div><p>Second Item</p></div>
</li>
</ul>`,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.tiptap__editor ul > li input[type=checkbox]')
.should('exist')
cy.get('.tiptap__editor h1')
.contains('Lorem Ipsum')
.should('exist')
cy.get('.tiptap__editor p')
.contains('Dolor sit amet')
.should('exist')
})
it('Should render an image from attachment', async () => {
TaskAttachmentFactory.truncate()
const tasks = TaskFactory.create(1, {
id: 1,
description: '',
})
cy.readFile('cypress/fixtures/image.jpg', null).then(file => {
const formData = new FormData()
formData.append('files', new Blob([file]), 'image.jpg')
cy.request({
method: 'PUT',
url: `${Cypress.env('API_URL')}/tasks/${tasks[0].id}/attachments`,
headers: {
'Authorization': `Bearer ${window.localStorage.getItem('token')}`,
'Content-Type': 'multipart/form-data',
},
body: formData,
})
.then(({body}) => {
const dec = new TextDecoder('utf-8')
const {success} = JSON.parse(dec.decode(body))
TaskFactory.create(1, {
id: 1,
description: `<img src="${Cypress.env('API_URL')}/tasks/${tasks[0].id}/attachments/${success[0].id}" alt="test image">`,
})
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.tiptap__editor img')
.should('be.visible')
.and(($img) => {
// "naturalWidth" and "naturalHeight" are set when the image loads
expect($img[0].naturalWidth).to.be.greaterThan(0)
})
})
})
})
})
})

View File

@ -1,11 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["./**/*", "../support/**/*", "../factories/**/*"],
"compilerOptions": {
"baseUrl": ".",
"isolatedModules": false,
"target": "ES2015",
"lib": ["ESNext", "dom"],
"types": ["cypress"]
"types": ["cypress"],
"ignoreDeprecations": "5.0"
}
}

View File

@ -1,36 +1,41 @@
import {UserFactory} from '../../factories/user'
import {ProjectFactory} from '../../factories/project'
const testAndAssertFailed = fixture => {
cy.intercept(Cypress.env('API_URL') + '/login*').as('login')
cy.visit('/login')
cy.get('input[id=username]').type(fixture.username)
cy.get('input[id=password]').type(fixture.password)
cy.get('.button').contains('Login').click()
cy.wait(5000) // It can take waaaayy too long to log the user in
cy.wait('@login')
cy.url().should('include', '/')
cy.get('div.message.danger').contains('Wrong username or password.')
}
const username = 'test'
const credentials = {
username: 'test',
password: '1234',
}
function login() {
cy.get('input[id=username]').type(credentials.username)
cy.get('input[id=password]').type(credentials.password)
cy.get('.button').contains('Login').click()
cy.url().should('include', '/')
}
context('Login', () => {
beforeEach(() => {
UserFactory.create(1, {username})
UserFactory.create(1, {username: credentials.username})
})
it('Should log in with the right credentials', () => {
const fixture = {
username: 'test',
password: '1234',
}
cy.visit('/login')
cy.get('input[id=username]').type(fixture.username)
cy.get('input[id=password]').type(fixture.password)
cy.get('.button').contains('Login').click()
cy.url().should('include', '/')
login()
cy.clock(1625656161057) // 13:00
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
cy.get('h2').should('contain', `Hi ${credentials.username}!`)
})
it('Should fail with a bad password', () => {
@ -55,4 +60,15 @@ context('Login', () => {
cy.visit('/')
cy.url().should('include', '/login')
})
it('Should redirect to the previous route after logging in', () => {
const projects = ProjectFactory.create(1)
cy.visit(`/projects/${projects[0].id}/list`)
cy.url().should('include', '/login')
login()
cy.url().should('include', `/projects/${projects[0].id}/list`)
})
})

View File

@ -1,10 +1,10 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {createLists} from '../list/prepareLists'
import {createProjects} from '../project/prepareProjects'
function logout() {
cy.get('.navbar .user .username')
cy.get('.navbar .username-dropdown-trigger')
.click()
cy.get('.navbar .user .dropdown-menu .dropdown-item')
cy.get('.navbar .dropdown-item')
.contains('Logout')
.click()
}
@ -26,21 +26,21 @@ describe('Log out', () => {
})
})
it.skip('Should clear the list history after logging the user out', () => {
const lists = createLists()
cy.visit(`/lists/${lists[0].id}`)
it.skip('Should clear the project history after logging the user out', () => {
const projects = createProjects()
cy.visit(`/projects/${projects[0].id}`)
.then(() => {
expect(localStorage.getItem('listHistory')).to.not.eq(null)
expect(localStorage.getItem('projectHistory')).to.not.eq(null)
})
logout()
cy.wait(1000) // This makes re-loading of the list and associated entities (and the resulting error) visible
cy.wait(1000) // This makes re-loading of the project and associated entities (and the resulting error) visible
cy.url()
.should('contain', '/login')
.then(() => {
expect(localStorage.getItem('listHistory')).to.eq(null)
expect(localStorage.getItem('projectHistory')).to.eq(null)
})
})
})

View File

@ -17,7 +17,7 @@ context('Registration', () => {
it('Should work without issues', () => {
const fixture = {
username: 'testuser',
password: '123456',
password: '12345678',
email: 'testuser@example.com',
}
@ -31,10 +31,10 @@ context('Registration', () => {
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
})
it.only('Should fail', () => {
it('Should fail', () => {
const fixture = {
username: 'test',
password: '123456',
password: '12345678',
email: 'testuser@example.com',
}

View File

@ -37,7 +37,7 @@ describe('User Settings', () => {
cy.get('.global-notification')
.should('contain', 'Success')
cy.get('.navbar .user .username')
cy.get('.navbar .username-dropdown-trigger .username')
.should('contain', 'Lorem Ipsum')
})
})

View File

@ -10,7 +10,7 @@ export class BucketFactory extends Factory {
return {
id: '{increment}',
title: faker.lorem.words(3),
list_id: 1,
project_id: 1,
created_by_id: 1,
created: now.toISOString(),
updated: now.toISOString(),

View File

@ -10,7 +10,7 @@ export class LinkShareFactory extends Factory {
return {
id: '{increment}',
hash: faker.random.word(32),
list_id: 1,
project_id: 1,
right: 0,
sharing_type: 0,
shared_by_id: 1,

View File

@ -1,19 +0,0 @@
import {Factory} from '../support/factory'
import {faker} from '@faker-js/faker'
export class ListFactory extends Factory {
static table = 'lists'
static factory() {
const now = new Date()
return {
id: '{increment}',
title: faker.lorem.words(3),
owner_id: 1,
namespace_id: 1,
created: now.toISOString(),
updated: now.toISOString(),
}
}
}

View File

@ -1,8 +1,8 @@
import {faker} from '@faker-js/faker'
import {Factory} from '../support/factory'
import {faker} from '@faker-js/faker'
export class NamespaceFactory extends Factory {
static table = 'namespaces'
export class ProjectFactory extends Factory {
static table = 'projects'
static factory() {
const now = new Date()

View File

@ -11,7 +11,7 @@ export class TaskFactory extends Factory {
id: '{increment}',
title: faker.lorem.words(3),
done: false,
list_id: 1,
project_id: 1,
created_by_id: 1,
index: '{increment}',
position: '{increment}',

View File

@ -0,0 +1,18 @@
import {Factory} from '../support/factory'
export class TaskReminderFactory extends Factory {
static table = 'task_reminders'
static factory() {
const now = new Date()
return {
id: '{increment}',
task_id: 1,
reminder: now.toISOString(),
created: now.toISOString(),
relative_to: '',
relative_period: 0,
}
}
}

View File

@ -1,14 +1,14 @@
import {Factory} from '../support/factory'
export class UserListFactory extends Factory {
static table = 'users_lists'
export class UserProjectFactory extends Factory {
static table = 'users_projects'
static factory() {
const now = new Date()
return {
id: '{increment}',
list_id: 1,
project_id: 1,
user_id: 1,
right: 0,
created: now.toISOString(),

View File

@ -4,7 +4,7 @@ import {seed} from './seed'
* A factory makes it easy to seed the database with data.
*/
export class Factory {
static table = null
static table: string | null = null
static factory() {
return {}

3
docker/injector.sh Normal file → Executable file
View File

@ -11,5 +11,8 @@ VIKUNJA_SENTRY_DSN="$(echo "$VIKUNJA_SENTRY_DSN" | sed -r 's/([:;])/\\\1/g')"
sed -ri "s:^(\s*window.API_URL\s*=)\s*.+:\1 '${VIKUNJA_API_URL}':g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.SENTRY_ENABLED\s*=)\s*.+:\1 ${VIKUNJA_SENTRY_ENABLED}:g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.SENTRY_DSN\s*=)\s*.+:\1 '${VIKUNJA_SENTRY_DSN}':g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.PROJECT_INFINITE_NESTING_ENABLED\s*=)\s*.+:\1 '${VIKUNJA_PROJECT_INFINITE_NESTING_ENABLED}':g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.ALLOW_ICON_CHANGES\s*=)\s*.+:\1 ${VIKUNJA_ALLOW_ICON_CHANGES}:g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.CUSTOM_LOGO_URL\s*=)\s*.+:\1 ${VIKUNJA_CUSTOM_LOGO_URL}:g" /usr/share/nginx/html/index.html
date -uIseconds | xargs echo 'info: started at'

19
docker/ipv6-disable.sh Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env sh
set -e
if [ ! -f "/proc/net/if_inet6" ]; then
echo "info: IPv6 is not available! Removing IPv6 listen configuration"
find /etc/nginx/conf.d -name '*.conf' -type f | \
while IFS= read -r CONFIG; do
sed -r '/^\s*listen\s*\[::\]:.+$/d' "$CONFIG" > "$CONFIG.temp"
if ! diff -U 5 "$CONFIG" "$CONFIG.temp" > "$CONFIG.diff"; then
echo "info: Removing IPv6 lines from $CONFIG" | \
cat - "$CONFIG.diff"
echo "# IPv6 is disabled because /proc/net/if_inet6 was not found" | \
cat - "$CONFIG.temp" > "$CONFIG"
else
echo "info: Skipping $CONFIG because it does not have IPv6 listen"
fi
rm -f "$CONFIG.temp" "$CONFIG.diff"
done
fi

View File

@ -4,7 +4,6 @@
pid /tmp/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
multi_accept on;

View File

@ -29,6 +29,20 @@ server {
try_files $uri /index.html =404;
}
# Disable caching for sw
location = /sw.js {
autoindex off;
expires off;
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
}
# Disable caching for webmanifest
location = /manifest.webmanifest {
autoindex off;
expires off;
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
}
# favicon.ico
location = /favicon.ico {
log_not_found off;

View File

@ -30,21 +30,21 @@ A basic service can look like this:
```javascript
import AbstractService from './abstractService'
import ListModel from '../models/list'
import ProjectModel from '../models/project'
export default class ListService extends AbstractService {
export default class ProjectService extends AbstractService {
constructor() {
super({
getAll: '/lists',
get: '/lists/{id}',
create: '/namespaces/{namespaceID}/lists',
update: '/lists/{id}',
delete: '/lists/{id}',
getAll: '/projects',
get: '/projects/{id}',
create: '/namespaces/{namespaceID}/projects',
update: '/projects/{id}',
delete: '/projects/{id}',
})
}
modelFactory(data) {
return new ListModel(data)
return new ProjectModel(data)
}
}
```
@ -132,7 +132,7 @@ import AbstractModel from './abstractModel'
import TaskModel from './task'
import UserModel from './user'
export default class ListModel extends AbstractModel {
export default class ProjectModel extends AbstractModel {
constructor(data) {
// The constructor of AbstractModel handles all the default parsing.

21
env.d.ts vendored
View File

@ -3,7 +3,28 @@
/// <reference types="cypress" />
/// <reference types="@histoire/plugin-vue/components" />
declare module 'postcss-focus-within/browser' {
import focusWithinInit from 'postcss-focus-within/browser'
export default focusWithinInit
}
declare module 'css-has-pseudo/browser' {
import cssHasPseudo from 'css-has-pseudo/browser'
export default cssHasPseudo
}
interface ImportMetaEnv {
readonly VIKUNJA_API_URL?: string
readonly VIKUNJA_HTTP_PORT?: number
readonly VIKUNJA_HTTPS_PORT?: number
readonly VIKUNJA_SENTRY_ENABLED?: boolean
readonly VIKUNJA_SENTRY_DSN?: string
readonly SENTRY_AUTH_TOKEN?: string
readonly SENTRY_ORG?: string
readonly SENTRY_PROJECT?: string
readonly VITE_IS_ONLINE: boolean
}

View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1664753041,
"narHash": "sha256-0ogaD8PaGHluARFeupofvk1Nq9gpVeZdlFM0Kcwguys=",
"lastModified": 1701336116,
"narHash": "sha256-kEmpezCR/FpITc6yMbAh4WrOCiT2zg5pSjnKrq51h5Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62844b302507c7531ad68a86cb7aa54704c9cb4",
"rev": "f5c27c6136db4d76c30e533c20517df6864c46ee",
"type": "github"
},
"original": {

View File

@ -28,7 +28,7 @@ export default defineConfig({
// light: './img/light.png',
// dark: './img/dark.png',
// },
// logoHref: 'https://acme.com',
logoHref: 'https://vikunja.io',
// favicon: './favicon.ico',
},
})

View File

@ -27,6 +27,13 @@
// our sentry instance to notify us of potential problems.
window.SENTRY_ENABLED = false
window.SENTRY_DSN = 'https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480'
// If enabled, allows the user to nest projects infinitely, instead of the default 2 levels.
// This setting might change in the future or be removed completely.
window.PROJECT_INFINITE_NESTING_ENABLED = false
// Allow changing the logo and other icons based on various occasions throughout the year.
window.ALLOW_ICON_CHANGES = true
// Allow using a custom logo via external URL.
window.CUSTOM_LOGO_URL = ''
</script>
</body>
</html>

Binary file not shown.

Binary file not shown.

View File

@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@7.26.2",
"packageManager": "pnpm@8.15.1",
"keywords": [
"todo",
"productivity",
@ -22,6 +22,7 @@
"gantt",
"kanban"
],
"type": "module",
"scripts": {
"serve": "vite",
"preview": "vite preview --port 4173",
@ -29,7 +30,8 @@
"build": "vite build && workbox copyLibraries dist/",
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
"build:dev": "vite build --mode development --outDir dist-dev/",
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
"lint": "eslint 'src/**/*.{js,ts,vue}'",
"lint:fix": "pnpm run lint --fix",
"test:e2e": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome'",
"test:e2e-record": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome --record'",
"test:e2e-dev-dev": "start-server-and-test preview:dev http://127.0.0.1:4173 'cypress open --e2e'",
@ -45,103 +47,141 @@
"story:preview": "histoire preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.2.1",
"@fortawesome/free-regular-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/vue-fontawesome": "3.0.3",
"@github/hotkey": "2.0.1",
"@infectoone/vue-ganttastic": "2.1.4",
"@intlify/unplugin-vue-i18n": "0.8.1",
"@kyvg/vue3-notification": "2.8.0",
"@sentry/tracing": "7.34.0",
"@sentry/vue": "7.34.0",
"@types/is-touch-device": "1.0.0",
"@types/lodash.clonedeep": "4.5.7",
"@types/sortablejs": "1.15.0",
"@vueuse/core": "9.12.0",
"axios": "1.2.6",
"blurhash": "2.0.4",
"@fortawesome/fontawesome-svg-core": "6.5.1",
"@fortawesome/free-regular-svg-icons": "6.5.1",
"@fortawesome/free-solid-svg-icons": "6.5.1",
"@fortawesome/vue-fontawesome": "3.0.6",
"@github/hotkey": "3.1.0",
"@infectoone/vue-ganttastic": "2.2.0",
"@intlify/unplugin-vue-i18n": "2.0.0",
"@kyvg/vue3-notification": "3.1.4",
"@sentry/tracing": "7.100.1",
"@sentry/vue": "7.100.1",
"@tiptap/core": "2.2.1",
"@tiptap/extension-blockquote": "2.2.1",
"@tiptap/extension-bold": "2.2.1",
"@tiptap/extension-bullet-list": "2.2.1",
"@tiptap/extension-code": "2.2.1",
"@tiptap/extension-code-block-lowlight": "2.2.1",
"@tiptap/extension-document": "2.2.1",
"@tiptap/extension-dropcursor": "2.2.1",
"@tiptap/extension-gapcursor": "2.2.1",
"@tiptap/extension-hard-break": "2.2.1",
"@tiptap/extension-heading": "2.2.1",
"@tiptap/extension-history": "2.2.1",
"@tiptap/extension-horizontal-rule": "2.2.1",
"@tiptap/extension-image": "2.2.1",
"@tiptap/extension-italic": "2.2.1",
"@tiptap/extension-link": "2.2.1",
"@tiptap/extension-list-item": "2.2.1",
"@tiptap/extension-ordered-list": "2.2.1",
"@tiptap/extension-paragraph": "2.2.1",
"@tiptap/extension-placeholder": "2.2.1",
"@tiptap/extension-strike": "2.2.1",
"@tiptap/extension-table": "2.2.1",
"@tiptap/extension-table-cell": "2.2.1",
"@tiptap/extension-table-header": "2.2.1",
"@tiptap/extension-table-row": "2.2.1",
"@tiptap/extension-task-item": "2.2.1",
"@tiptap/extension-task-list": "2.2.1",
"@tiptap/extension-text": "2.2.1",
"@tiptap/extension-typography": "2.2.1",
"@tiptap/extension-underline": "2.2.1",
"@tiptap/pm": "2.2.1",
"@tiptap/suggestion": "2.2.1",
"@tiptap/vue-3": "2.2.1",
"@types/is-touch-device": "1.0.2",
"@types/lodash.clonedeep": "4.5.9",
"@vueuse/core": "10.7.2",
"@vueuse/router": "10.7.2",
"axios": "1.6.7",
"blurhash": "2.0.5",
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
"codemirror": "5.65.11",
"date-fns": "2.29.3",
"dayjs": "1.11.7",
"dompurify": "2.4.3",
"easymde": "2.18.0",
"date-fns": "3.3.1",
"dayjs": "1.11.10",
"dompurify": "3.0.8",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.21",
"floating-vue": "2.0.0-beta.20",
"focus-within": "3.0.2",
"highlight.js": "11.7.0",
"flexsearch": "0.7.31",
"floating-vue": "5.2.2",
"is-touch-device": "1.0.1",
"lodash.clonedeep": "4.5.0",
"klona": "2.0.6",
"lodash.debounce": "4.0.8",
"marked": "4.2.12",
"minimist": "1.2.7",
"pinia": "2.0.29",
"lowlight": "2.9.0",
"pinia": "2.1.7",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"sortablejs": "1.15.0",
"ufo": "1.0.1",
"vue": "3.2.45",
"sortablejs": "1.15.2",
"tippy.js": "6.3.7",
"ufo": "1.4.0",
"vue": "3.4.15",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.1",
"vue-i18n": "9.2.2",
"vue-router": "4.1.6",
"workbox-precaching": "6.5.4",
"vue-flatpickr-component": "11.0.3",
"vue-i18n": "9.9.1",
"vue-router": "4.2.5",
"workbox-precaching": "7.0.0",
"zhyswan-vuedraggable": "4.1.3"
},
"devDependencies": {
"@4tw/cypress-drag-drop": "2.2.3",
"@cypress/vite-dev-server": "5.0.2",
"@cypress/vue": "5.0.3",
"@faker-js/faker": "7.6.0",
"@histoire/plugin-screenshot": "0.12.4",
"@histoire/plugin-vue": "0.12.4",
"@rushstack/eslint-patch": "1.2.0",
"@types/codemirror": "5.60.7",
"@types/dompurify": "2.4.0",
"@types/flexsearch": "0.7.3",
"@types/focus-within": "1.0.1",
"@types/lodash.debounce": "4.0.7",
"@types/marked": "4.0.8",
"@types/node": "18.11.18",
"@4tw/cypress-drag-drop": "2.2.5",
"@cypress/vite-dev-server": "5.0.7",
"@cypress/vue": "6.0.0",
"@faker-js/faker": "8.4.0",
"@histoire/plugin-screenshot": "0.17.8",
"@histoire/plugin-vue": "0.17.9",
"@rushstack/eslint-patch": "1.7.2",
"@tsconfig/node18": "18.2.2",
"@types/codemirror": "5.60.15",
"@types/dompurify": "3.0.5",
"@types/flexsearch": "0.7.6",
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
"@types/node": "20.11.10",
"@types/postcss-preset-env": "7.7.0",
"@typescript-eslint/eslint-plugin": "5.49.0",
"@typescript-eslint/parser": "5.49.0",
"@vitejs/plugin-legacy": "3.0.2",
"@vitejs/plugin-vue": "4.0.0",
"@vue/eslint-config-typescript": "11.0.2",
"@vue/test-utils": "2.2.8",
"@vue/tsconfig": "0.1.3",
"autoprefixer": "10.4.13",
"browserslist": "4.21.4",
"caniuse-lite": "1.0.30001449",
"csstype": "3.1.1",
"cypress": "12.4.1",
"esbuild": "0.17.5",
"eslint": "8.33.0",
"eslint-plugin-vue": "9.9.0",
"happy-dom": "8.2.0",
"histoire": "0.12.4",
"netlify-cli": "12.10.0",
"postcss": "8.4.21",
"@types/sortablejs": "1.15.7",
"@typescript-eslint/eslint-plugin": "6.20.0",
"@typescript-eslint/parser": "6.20.0",
"@vitejs/plugin-legacy": "5.3.0",
"@vitejs/plugin-vue": "5.0.3",
"@vue/eslint-config-typescript": "12.0.0",
"@vue/test-utils": "2.4.4",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.17",
"browserslist": "4.22.3",
"caniuse-lite": "1.0.30001581",
"css-has-pseudo": "6.0.1",
"csstype": "3.1.3",
"cypress": "13.6.3",
"esbuild": "0.20.0",
"eslint": "8.56.0",
"eslint-plugin-vue": "9.20.1",
"happy-dom": "13.3.5",
"histoire": "0.17.9",
"postcss": "8.4.33",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "3.0.1",
"postcss-preset-env": "7.8.3",
"rollup": "3.12.0",
"rollup-plugin-visualizer": "5.9.0",
"sass": "1.57.1",
"start-server-and-test": "1.15.3",
"typescript": "4.9.4",
"vite": "4.0.4",
"vite-plugin-inject-preload": "1.2.0",
"vite-plugin-pwa": "0.14.1",
"vite-svg-loader": "4.0.0",
"vitest": "0.28.3",
"vue-tsc": "1.0.24",
"wait-on": "7.0.1",
"workbox-cli": "6.5.4"
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.1",
"postcss-preset-env": "9.3.0",
"rollup": "4.9.6",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.70.0",
"start-server-and-test": "2.0.3",
"typescript": "5.3.3",
"vite": "5.0.12",
"vite-plugin-inject-preload": "1.3.3",
"vite-plugin-pwa": "0.17.5",
"vite-plugin-sentry": "1.3.0",
"vite-svg-loader": "5.1.0",
"vitest": "1.2.2",
"vue-tsc": "1.8.27",
"wait-on": "7.2.0",
"workbox-cli": "7.0.0"
},
"pnpm": {
"patchedDependencies": {
"flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch"
}
}
}

View File

@ -0,0 +1,16 @@
diff --git a/index.d.ts b/index.d.ts
deleted file mode 100644
index 9f39f41073864b83968bdaa242ac4e3c3149685a..0000000000000000000000000000000000000000
diff --git a/package.json b/package.json
index 8968f5bf8010ff194240591c8b83299f7328e79d..6d84b6f590a841b129ed8b3860cb786df5a185c0 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,6 @@
},
"main": "dist/flexsearch.bundle.js",
"browser": "dist/flexsearch.bundle.js",
- "module": "dist/module/index.js",
- "types": "./index.d.ts",
"preferGlobal": false,
"repository": {
"type": "git",

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,16 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"labels": ["dependencies"],
"extends": [
"config:base"
"config:js-app"
],
"hostRules": [
{
"timeout": 600000
}
],
"packageRules": [
{
"matchPackageNames": ["netlify-cli", "happy-dom"],
"matchPackageNames": ["happy-dom"],
"extends": ["schedule:weekly"]
},
{
@ -20,11 +25,24 @@
"@vueuse/"
]
},
{
"groupName": "histoire",
"matchPackagePrefixes": [
"@histoire/",
"histoire"
]
},
{
"groupName": "tiptap",
"matchPackagePrefixes": [
"@tiptap/",
"tiptap"
]
},
{
"matchDepTypes": ["devDependencies"],
"automerge": true,
"automergeStrategy": "squash",
"automergeType": "pr"
"groupName": "dev-dependencies",
"extends": ["schedule:daily"]
}
]
}

View File

@ -33,9 +33,9 @@ const promiseExec = cmd => {
}
(async function () {
let stdout = await promiseExec(`./node_modules/.bin/netlify link --id ${siteId}`)
let stdout = await promiseExec(`/home/node/docker-netlify-cli/node_modules/.bin/netlify link --id ${siteId}`)
console.log(stdout)
stdout = await promiseExec(`./node_modules/.bin/netlify deploy --alias ${alias}`)
stdout = await promiseExec(`/home/node/docker-netlify-cli/node_modules/.bin/netlify deploy --alias ${alias}`)
console.log(stdout)
const data = await fetch(prIssueCommentsUrl).then(response => response.json())

View File

@ -1 +1 @@
57af69409e66bc87f4f2fc5822dd8d3c2eb47c601f81af1ac4a56f3e2d80837b1a2de06f4ff57695ec379b7c15b881e3 ./scripts/deploy-preview-netlify.mjs
4a7c1293c7b12e9ab476cdf35251a407c6a1cd005d22c06df994222cccfb25cde5f47d15866a098c9d739778fee4dc19 ./scripts/deploy-preview-netlify.mjs

View File

@ -1,17 +1,23 @@
<template>
<ready>
<Ready>
<template v-if="authUser">
<TheNavigation/>
<content-auth/>
<TheNavigation />
<ContentAuth />
</template>
<content-link-share v-else-if="authLinkShare"/>
<no-auth-wrapper v-else>
<router-view/>
</no-auth-wrapper>
<Notification/>
<ContentLinkShare v-else-if="authLinkShare" />
<NoAuthWrapper v-else>
<router-view />
</NoAuthWrapper>
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
</ready>
<KeyboardShortcuts v-if="keyboardShortcutsActive" />
<Teleport to="body">
<AddToHomeScreen />
<UpdateNotification />
<Notification />
<DemoMode />
</Teleport>
</Ready>
</template>
<script lang="ts" setup>
@ -19,23 +25,29 @@ import {computed, watch} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {useI18n} from 'vue-i18n'
import isTouchDevice from 'is-touch-device'
import {success} from '@/message'
import Notification from '@/components/misc/notification.vue'
import KeyboardShortcuts from './components/misc/keyboard-shortcuts/index.vue'
import UpdateNotification from '@/components/home/UpdateNotification.vue'
import KeyboardShortcuts from '@/components/misc/keyboard-shortcuts/index.vue'
import TheNavigation from '@/components/home/TheNavigation.vue'
import ContentAuth from './components/home/contentAuth.vue'
import ContentLinkShare from './components/home/contentLinkShare.vue'
import ContentAuth from '@/components/home/contentAuth.vue'
import ContentLinkShare from '@/components/home/contentLinkShare.vue'
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import Ready from '@/components/misc/ready.vue'
import {setLanguage} from './i18n'
import AccountDeleteService from '@/services/accountDelete'
import {setLanguage} from '@/i18n'
import {useAuthStore} from '@/stores/auth'
import {useBaseStore} from '@/stores/base'
import {useColorScheme} from '@/composables/useColorScheme'
import {useBodyClass} from '@/composables/useBodyClass'
import {useAuthStore} from './stores/auth'
import AddToHomeScreen from '@/components/home/AddToHomeScreen.vue'
import DemoMode from '@/components/home/DemoMode.vue'
const importAccountDeleteService = () => import('@/services/accountDelete')
const importMessage = () => import('@/message')
const baseStore = useBaseStore()
const authStore = useAuthStore()
@ -57,8 +69,11 @@ watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
return
}
const messageP = importMessage()
const AccountDeleteService = (await importAccountDeleteService()).default
const accountDeletionService = new AccountDeleteService()
await accountDeletionService.confirm(accountDeletionConfirm)
const {success} = await messageP
success({message: t('user.deletion.confirmSuccess')})
authStore.refreshUserInfo()
}, { immediate: true })
@ -85,7 +100,7 @@ watch(userEmailConfirm, (userEmailConfirm) => {
router.push({name: 'user.login'})
}, { immediate: true })
setLanguage()
setLanguage(authStore.settings.language)
useColorScheme()
</script>

Binary file not shown.

4
src/assets/checkbox.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 18 18" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z" stroke-dasharray="60"></path>
<polyline points="1 9 7 14 15 4" stroke-dasharray="22" stroke-dashoffset="66"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 313 KiB

View File

@ -21,10 +21,16 @@ const state = reactive({
</script>
<template>
<Story :setup-app="setupApp" :layout="{ type: 'grid', width: '200px' }">
<Story
:setup-app="setupApp"
:layout="{ type: 'grid', width: '200px' }"
>
<Variant title="custom">
<template #controls>
<HstCheckbox v-model="state.disabled" title="Disabled" />
<HstCheckbox
v-model="state.disabled"
title="Disabled"
/>
</template>
<BaseButton :disabled="state.disabled">
Hello!

View File

@ -6,38 +6,39 @@
<template>
<div
v-if="disabled === true && (to !== undefined || href !== undefined)"
ref="button"
class="base-button"
:aria-disabled="disabled || undefined"
ref="button"
>
<slot/>
<slot />
</div>
<router-link
v-else-if="to !== undefined"
ref="button"
:to="to"
class="base-button"
ref="button"
>
<slot/>
<slot />
</router-link>
<a v-else-if="href !== undefined"
<a
v-else-if="href !== undefined"
ref="button"
class="base-button"
:href="href"
rel="noreferrer noopener nofollow"
target="_blank"
ref="button"
>
<slot/>
<slot />
</a>
<button
v-else
ref="button"
:type="type"
class="base-button base-button--type-button"
:disabled="disabled || undefined"
ref="button"
@click="(event: MouseEvent) => emit('click', event)"
>
<slot/>
<slot />
</button>
</template>
@ -63,7 +64,7 @@ import {unrefElement} from '@vueuse/core'
import {ref, type HTMLAttributes} from 'vue'
import type {RouteLocationRaw} from 'vue-router'
export interface BaseButtonProps extends HTMLAttributes {
export interface BaseButtonProps extends /* @vue-ignore */ HTMLAttributes {
type?: BaseButtonTypes
disabled?: boolean
to?: RouteLocationRaw

View File

@ -0,0 +1,63 @@
<template>
<div
v-cy="'checkbox'"
class="base-checkbox"
>
<input
:id="checkboxId"
type="checkbox"
class="is-sr-only"
:checked="modelValue"
:disabled="disabled || undefined"
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
>
<slot
name="label"
:checkbox-id="checkboxId"
>
<label
:for="checkboxId"
class="base-checkbox__label"
>
<slot />
</label>
</slot>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import {createRandomID} from '@/helpers/randomId'
defineProps({
modelValue: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
})
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
}>()
const checkboxId = ref(`fancycheckbox_${createRandomID()}`)
</script>
<style lang="scss" scoped>
.base-checkbox__label {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
display: inline-flex;
}
.base-checkbox:has(input:disabled) .base-checkbox__label {
cursor:not-allowed;
pointer-events: none;
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<transition
name="expandable-slide"
@before-enter="beforeEnter"
@beforeEnter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@afterEnter="afterEnter"
@enterCancelled="enterCancelled"
@beforeLeave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
@afterLeave="afterLeave"
@leaveCancelled="leaveCancelled"
>
<div
v-if="initialHeight"
@ -18,7 +18,10 @@
>
<slot />
</div>
<div v-else-if="open" class="expandable">
<div
v-else-if="open"
class="expandable"
>
<slot />
</div>
</transition>
@ -32,7 +35,7 @@ import {computed, ref} from 'vue'
import {getInheritedBackgroundColor} from '@/helpers/getInheritedBackgroundColor'
const props = defineProps({
/** Wheather the Expandable is open or not */
/** Whether the Expandable is open or not */
open: {
type: Boolean,
default: false,

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import datemathHelp from './datemathHelp.vue'
</script>
<template>
<Story>
<Variant title="Default">
<datemathHelp />
</Variant>
</Story>
</template>

View File

@ -1,37 +1,46 @@
<template>
<card
class="has-no-shadow how-it-works-modal"
:title="$t('input.datemathHelp.title')">
:title="$t('input.datemathHelp.title')"
>
<p>
{{ $t('input.datemathHelp.intro') }}
</p>
<p>
<i18n-t keypath="input.datemathHelp.expression" scope="global">
<i18n-t
keypath="input.datemathHelp.expression"
scope="global"
>
<code>now</code>
<code>||</code>
</i18n-t>
</p>
<p>
<i18n-t keypath="input.datemathHelp.similar" scope="global">
<i18n-t
keypath="input.datemathHelp.similar"
scope="global"
>
<BaseButton
href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/"
target="_blank">
target="_blank"
>
Grafana
</BaseButton>
<BaseButton
href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/common-options.html#date-math"
target="_blank">
target="_blank"
>
Elasticsearch
</BaseButton>
</i18n-t>
</p>
<p>{{ $t('misc.forExample') }}</p>
<ul>
<li><code>+1d</code>{{ $t('input.datemathHelp.add1Day') }}</li>
<li><code>-1d</code>{{ $t('input.datemathHelp.minus1Day') }}</li>
<li><code>/d</code>{{ $t('input.datemathHelp.roundDay') }}</li>
<li><code>+1d</code> {{ $t('input.datemathHelp.add1Day') }}</li>
<li><code>-1d</code> {{ $t('input.datemathHelp.minus1Day') }}</li>
<li><code>/d</code> {{ $t('input.datemathHelp.roundDay') }}</li>
</ul>
<p>{{ $t('input.datemathHelp.supportedUnits') }}</p>
<h3>{{ $t('input.datemathHelp.supportedUnits') }}</h3>
<table class="table">
<tbody>
<tr>
@ -69,7 +78,7 @@
</tbody>
</table>
<p>{{ $t('input.datemathHelp.someExamples') }}</p>
<h3>{{ $t('input.datemathHelp.someExamples') }}</h3>
<table class="table">
<tbody>
<tr>
@ -99,8 +108,11 @@
<tr>
<td><code>{{ exampleDate }}||+1M/d</code></td>
<td>
<i18n-t keypath="input.datemathHelp.examples.datePlusMonth" scope="global">
<code>{{ exampleDate }}</code>
<i18n-t
keypath="input.datemathHelp.examples.datePlusMonth"
scope="global"
>
<strong>{{ exampleDate }}</strong>
</i18n-t>
</td>
</tr>
@ -110,13 +122,15 @@
</template>
<script lang="ts" setup>
import {formatDate} from '@/helpers/time/formatDate'
import {formatDateShort} from '@/helpers/time/formatDate'
import BaseButton from '@/components/base/BaseButton.vue'
const exampleDate = formatDate(new Date(), 'yyyy-MM-dd')
const exampleDate = formatDateShort(new Date())
</script>
<style scoped lang="scss">
// FIXME: Remove style overwrites
.how-it-works-modal {
font-size: 1rem;
}

View File

@ -1,20 +1,31 @@
<template>
<div class="datepicker-with-range-container">
<popup>
<Popup>
<template #trigger="{toggle}">
<slot name="trigger" :toggle="toggle" :buttonText="buttonText"></slot>
<slot
name="trigger"
:toggle="toggle"
:button-text="buttonText"
/>
</template>
<template #content="{isOpen}">
<div class="datepicker-with-range" :class="{'is-open': isOpen}">
<div
class="datepicker-with-range"
:class="{'is-open': isOpen}"
>
<div class="selections">
<BaseButton @click="setDateRange(null)" :class="{'is-active': customRangeActive}">
<BaseButton
:class="{'is-active': customRangeActive}"
@click="setDateRange(null)"
>
{{ $t('misc.custom') }}
</BaseButton>
<BaseButton
v-for="(value, text) in DATE_RANGES"
:key="text"
:class="{'is-active': from === value[0] && to === value[1]}"
@click="setDateRange(value)"
:class="{'is-active': from === value[0] && to === value[1]}">
>
{{ $t(`input.datepickerRange.ranges.${text}`) }}
</BaseButton>
</div>
@ -23,10 +34,18 @@
{{ $t('input.datepickerRange.from') }}
<div class="field has-addons">
<div class="control is-fullwidth">
<input class="input" type="text" v-model="from"/>
<input
v-model="from"
class="input"
type="text"
>
</div>
<div class="control">
<x-button icon="calendar" variant="secondary" data-toggle/>
<x-button
icon="calendar"
variant="secondary"
data-toggle
/>
</div>
</div>
</label>
@ -34,38 +53,49 @@
{{ $t('input.datepickerRange.to') }}
<div class="field has-addons">
<div class="control is-fullwidth">
<input class="input" type="text" v-model="to"/>
<input
v-model="to"
class="input"
type="text"
>
</div>
<div class="control">
<x-button icon="calendar" variant="secondary" data-toggle/>
<x-button
icon="calendar"
variant="secondary"
data-toggle
/>
</div>
</div>
</label>
<flat-pickr
:config="flatPickerConfig"
v-model="flatpickrRange"
:config="flatPickerConfig"
/>
<p>
{{ $t('input.datemathHelp.canuse') }}
<BaseButton class="has-text-primary" @click="showHowItWorks = true">
<BaseButton
class="has-text-primary"
@click="showHowItWorks = true"
>
{{ $t('input.datemathHelp.learnhow') }}
</BaseButton>
</p>
<modal
:enabled="showHowItWorks"
@close="() => showHowItWorks = false"
transition-name="fade"
:overflow="true"
variant="hint-modal"
@close="() => showHowItWorks = false"
>
<DatemathHelp/>
<DatemathHelp />
</modal>
</div>
</div>
</template>
</popup>
</Popup>
</div>
</template>
@ -75,25 +105,24 @@ import {useI18n} from 'vue-i18n'
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
import Popup from '@/components/misc/popup.vue'
import {DATE_RANGES} from '@/components/date/dateRanges'
import BaseButton from '@/components/base/BaseButton.vue'
import DatemathHelp from '@/components/date/datemathHelp.vue'
import {useAuthStore} from '@/stores/auth'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const authStore = useAuthStore()
const {t} = useI18n({useScope: 'global'})
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: {
required: false,
},
})
// FIXME: This seems to always contain the default value - that breaks the picker
const weekStart = computed(() => authStore.settings.weekStart ?? 0)
const emit = defineEmits(['update:modelValue'])
const {t} = useI18n({useScope: 'global'})
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,
@ -101,9 +130,7 @@ const flatPickerConfig = computed(() => ({
enableTime: false,
wrap: true,
mode: 'range',
locale: {
firstDayOf7Days: weekStart.value,
},
locale: getFlatpickrLanguage(),
}))
const showHowItWorks = ref(false)
@ -120,9 +147,9 @@ watch(
to.value = newValue.dateTo
// Only set the date back to flatpickr when it's an actual date.
// Otherwise flatpickr runs in an endless loop and slows down the browser.
const dateFrom = new Date(from.value)
const dateTo = new Date(to.value)
if (dateTo.getTime() === dateTo.getTime() && dateFrom.getTime() === dateFrom.getTime()) {
const dateFrom = parseDateOrString(from.value, false)
const dateTo = parseDateOrString(to.value, false)
if (dateFrom instanceof Date && dateTo instanceof Date) {
flatpickrRange.value = `${from.value} to ${to.value}`
}
},

View File

@ -0,0 +1,86 @@
<template>
<div
v-if="shouldShowMessage"
class="add-to-home-screen"
:class="{'has-update-available': hasUpdateAvailable}"
>
<icon
icon="arrow-up-from-bracket"
class="add-icon"
/>
<p>
{{ $t('home.addToHomeScreen') }}
</p>
<BaseButton
class="hide-button"
@click="() => hideMessage = true"
>
<icon icon="x" />
</BaseButton>
</div>
</template>
<script lang="ts" setup>
import BaseButton from '@/components/base/BaseButton.vue'
import {useLocalStorage} from '@vueuse/core'
import {computed} from 'vue'
import {useBaseStore} from '@/stores/base'
const baseStore = useBaseStore()
const hideMessage = useLocalStorage('hideAddToHomeScreenMessage', false)
const hasUpdateAvailable = computed(() => baseStore.updateAvailable)
const shouldShowMessage = computed(() => {
if (hideMessage.value) {
return false
}
if (typeof window !== 'undefined' && window.matchMedia('(display-mode: standalone)').matches) {
return false
}
return true
})
</script>
<style lang="scss" scoped>
.add-to-home-screen {
position: fixed;
// FIXME: We should prevent usage of z-index or
// at least define it centrally
// the highest z-index of a modal is .hint-modal with 4500
z-index: 5000;
bottom: 1rem;
inset-inline: 1rem;
max-width: max-content;
margin-inline: auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: .5rem 1rem;
background: var(--grey-900);
border-radius: $radius;
font-size: .9rem;
color: var(--grey-200);
@media screen and (min-width: $tablet) {
display: none;
}
&.has-update-available {
bottom: 5rem;
}
}
.add-icon {
color: var(--primary-light);
}
.hide-button {
padding: .25rem .5rem;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,52 @@
<script setup lang="ts">
import {computed, ref} from 'vue'
import {useConfigStore} from '@/stores/config'
import BaseButton from '@/components/base/BaseButton.vue'
const configStore = useConfigStore()
const hide = ref(false)
const enabled = computed(() => configStore.demoModeEnabled && !hide.value)
</script>
<template>
<div
v-if="enabled"
class="demo-mode-banner"
>
<p>
{{ $t('demo.title') }}
<strong class="is-uppercase">{{ $t('demo.everythingWillBeDeleted') }}</strong>
</p>
<BaseButton
class="hide-button"
@click="() => hide = true"
>
<icon icon="times" />
</BaseButton>
</div>
</template>
<style scoped lang="scss">
.demo-mode-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--danger);
z-index: 100;
padding: .5rem;
text-align: center;
&, strong {
color: hsl(220, 13%, 91%) !important; // --grey-200 in light mode, hardcoded because the color should not change
}
}
.hide-button {
padding: .25rem .5rem;
cursor: pointer;
position: absolute;
right: .5rem;
top: .25rem;
}
</style>

View File

@ -4,17 +4,35 @@ import { useNow } from '@vueuse/core'
import LogoFull from '@/assets/logo-full.svg?component'
import LogoFullPride from '@/assets/logo-full-pride.svg?component'
import {MILLISECONDS_A_HOUR} from '@/constants/date'
const now = useNow()
const Logo = computed(() => now.value.getMonth() === 5 ? LogoFullPride : LogoFull)
const now = useNow({
interval: MILLISECONDS_A_HOUR,
})
const Logo = computed(() => window.ALLOW_ICON_CHANGES && now.value.getMonth() === 6 ? LogoFullPride : LogoFull)
const CustomLogo = computed(() => window.CUSTOM_LOGO_URL)
</script>
<template>
<Logo alt="Vikunja" class="logo" />
<div>
<Logo
v-if="!CustomLogo"
alt="Vikunja"
class="logo"
/>
<img
v-show="CustomLogo"
:src="CustomLogo"
alt="Vikunja"
class="logo"
>
</div>
</template>
<style lang="scss" scoped>
.logo {
color: var(--logo-text-color);
max-width: 168px;
max-height: 48px;
}
</style>

View File

@ -1,11 +1,11 @@
<template>
<BaseButton
v-shortcut="'Mod+e'"
class="menu-show-button"
@click="baseStore.toggleMenu()"
@shortkey="() => baseStore.toggleMenu()"
v-shortcut="'Control+e'"
:title="$t('keyboardShortcuts.toggleMenu')"
:aria-label="menuActive ? $t('misc.hideMenu') : $t('misc.showMenu')"
@click="baseStore.toggleMenu()"
@shortkey="() => baseStore.toggleMenu()"
/>
</template>

View File

@ -1,5 +1,9 @@
<template>
<BaseButton class="menu-bottom-link" :href="poweredByUrl" target="_blank">
<BaseButton
class="menu-bottom-link"
:href="poweredByUrl"
target="_blank"
>
{{ $t('misc.poweredBy') }}
</BaseButton>
</template>

View File

@ -0,0 +1,109 @@
<template>
<draggable
v-model="availableProjects"
animation="100"
ghost-class="ghost"
group="projects"
handle=".handle"
tag="menu"
item-key="id"
:disabled="!canEditOrder"
filter=".drag-disabled"
:component-data="{
type: 'transition-group',
name: !drag ? 'flip-list' : null,
class: [
'menu-list can-be-hidden',
{ 'dragging-disabled': !canEditOrder }
],
}"
@start="() => drag = true"
@end="saveProjectPosition"
>
<template #item="{element: project}">
<ProjectsNavigationItem
:class="{'drag-disabled': project.id < 0}"
:project="project"
:is-loading="projectUpdating[project.id]"
:can-collapse="canCollapse"
:level="level"
:data-project-id="project.id"
/>
</template>
</draggable>
</template>
<script lang="ts" setup>
import {ref, watch} from 'vue'
import draggable from 'zhyswan-vuedraggable'
import type {SortableEvent} from 'sortablejs'
import ProjectsNavigationItem from '@/components/home/ProjectsNavigationItem.vue'
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
import type {IProject} from '@/modelTypes/IProject'
import {useProjectStore} from '@/stores/projects'
const props = defineProps<{
modelValue?: IProject[],
canEditOrder: boolean,
canCollapse?: boolean,
level?: number,
}>()
const emit = defineEmits<{
(e: 'update:modelValue', projects: IProject[]): void
}>()
const drag = ref(false)
const projectStore = useProjectStore()
// Vue draggable will modify the projects list as it changes their position which will not work on a prop.
// Hence, we'll clone the prop and work on the clone.
const availableProjects = ref<IProject[]>([])
watch(
() => props.modelValue,
projects => {
availableProjects.value = projects || []
},
{immediate: true},
)
const projectUpdating = ref<{ [id: IProject['id']]: boolean }>({})
async function saveProjectPosition(e: SortableEvent) {
if (!e.newIndex && e.newIndex !== 0) return
const projectsActive = availableProjects.value
// If the project was dragged to the last position, Safari will report e.newIndex as the size of the projectsActive
// array instead of using the position. Because the index is wrong in that case, dragging the project will fail.
// To work around that we're explicitly checking that case here and decrease the index.
const newIndex = e.newIndex === projectsActive.length ? e.newIndex - 1 : e.newIndex
const projectId = parseInt(e.item.dataset.projectId)
const project = projectStore.projects[projectId]
const parentProjectId = e.to.parentNode.dataset.projectId ? parseInt(e.to.parentNode.dataset.projectId) : 0
const projectBefore = projectsActive[newIndex - 1] ?? null
const projectAfter = projectsActive[newIndex + 1] ?? null
projectUpdating.value[project.id] = true
const position = calculateItemPosition(
projectBefore !== null ? projectBefore.position : null,
projectAfter !== null ? projectAfter.position : null,
)
try {
// create a copy of the project in order to not violate pinia manipulation
await projectStore.updateProject({
...project,
position,
parentProjectId,
})
emit('update:modelValue', availableProjects.value)
} finally {
projectUpdating.value[project.id] = false
}
}
</script>

View File

@ -0,0 +1,199 @@
<template>
<li
class="list-menu loader-container is-loading-small"
:class="{'is-loading': isLoading}"
>
<div>
<BaseButton
v-if="canCollapse && childProjects?.length > 0"
class="collapse-project-button"
@click="childProjectsOpen = !childProjectsOpen"
>
<icon
icon="chevron-down"
:class="{ 'project-is-collapsed': !childProjectsOpen }"
/>
</BaseButton>
<BaseButton
:to="{ name: 'project.index', params: { projectId: project.id} }"
class="list-menu-link"
:class="{'router-link-exact-active': currentProject?.id === project.id}"
>
<span
v-if="!canCollapse || childProjects?.length === 0"
class="collapse-project-button-placeholder"
/>
<div
class="color-bubble-handle-wrapper"
:class="{'is-draggable': project.id > 0}"
>
<ColorBubble
v-if="project.hexColor !== ''"
:color="project.hexColor"
/>
<span
v-else-if="project.id < -1"
class="saved-filter-icon icon menu-item-icon"
>
<icon icon="filter" />
</span>
<span
v-if="project.id > 0"
class="icon menu-item-icon handle"
:class="{'has-color-bubble': project.hexColor !== ''}"
>
<icon icon="grip-lines" />
</span>
</div>
<span class="project-menu-title">{{ getProjectTitle(project) }}</span>
</BaseButton>
<BaseButton
v-if="project.id > 0"
class="favorite"
:class="{'is-favorite': project.isFavorite}"
@click="projectStore.toggleProjectFavorite(project)"
>
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']" />
</BaseButton>
<ProjectSettingsDropdown
class="menu-list-dropdown"
:project="project"
:level="level"
>
<template #trigger="{toggleOpen}">
<BaseButton
class="menu-list-dropdown-trigger"
@click="toggleOpen"
>
<icon
icon="ellipsis-h"
class="icon"
/>
</BaseButton>
</template>
</ProjectSettingsDropdown>
</div>
<ProjectsNavigation
v-if="canNestDeeper && childProjectsOpen && canCollapse"
:model-value="childProjects"
:can-edit-order="true"
:can-collapse="canCollapse"
:level="level + 1"
/>
</li>
</template>
<script setup lang="ts">
import {computed, ref} from 'vue'
import {useProjectStore} from '@/stores/projects'
import {useBaseStore} from '@/stores/base'
import type {IProject} from '@/modelTypes/IProject'
import BaseButton from '@/components/base/BaseButton.vue'
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
import {getProjectTitle} from '@/helpers/getProjectTitle'
import ColorBubble from '@/components/misc/colorBubble.vue'
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
import {canNestProjectDeeper} from '@/helpers/canNestProjectDeeper'
const {
project,
isLoading,
canCollapse,
level = 0,
} = defineProps<{
project: IProject,
isLoading?: boolean,
canCollapse?: boolean,
level?: number,
}>()
const projectStore = useProjectStore()
const baseStore = useBaseStore()
const currentProject = computed(() => baseStore.currentProject)
const childProjectsOpen = ref(true)
const childProjects = computed(() => {
if (!canNestDeeper.value) {
return []
}
return projectStore.getChildProjects(project.id)
.filter(p => !p.isArchived)
.sort((a, b) => a.position - b.position)
})
const canNestDeeper = computed(() => canNestProjectDeeper(level))
</script>
<style lang="scss" scoped>
.list-setting-spacer {
width: 5rem;
flex-shrink: 0;
}
.project-is-collapsed {
transform: rotate(-90deg);
}
.favorite {
transition: opacity $transition, color $transition;
opacity: 0;
&:hover,
&.is-favorite {
opacity: 1;
color: var(--warning);
}
}
.list-menu:hover > div > .favorite {
opacity: 1;
}
.list-menu:hover > div > a > .color-bubble-handle-wrapper.is-draggable > {
.saved-filter-icon,
.color-bubble {
opacity: 0;
}
}
.is-touch .color-bubble {
opacity: 1 !important;
}
.color-bubble-handle-wrapper {
position: relative;
width: 1rem;
height: 1rem;
display: flex;
align-items: center;
justify-content: flex-start;
margin-right: .25rem;
flex-shrink: 0;
.color-bubble, .icon {
transition: all $transition;
position: absolute;
width: 12px;
margin: 0 !important;
padding: 0 !important;
}
}
.project-menu-title {
overflow: hidden;
text-overflow: ellipsis;
}
.saved-filter-icon {
color: var(--grey-300) !important;
font-size: .75rem;
}
.is-touch .handle.has-color-bubble {
display: none !important;
}
</style>

View File

@ -1,119 +1,138 @@
<template>
<header
:class="{'has-background': background, 'menu-active': menuActive}"
:class="{ 'has-background': background, 'menu-active': menuActive }"
aria-label="main navigation"
class="navbar main-theme is-fixed-top d-print-none"
class="navbar d-print-none"
>
<router-link :to="{name: 'home'}" class="logo-link">
<Logo width="164" height="48"/>
<router-link
:to="{ name: 'home' }"
class="logo-link"
>
<Logo
width="164"
height="48"
/>
</router-link>
<MenuButton class="menu-button"/>
<div class="list-title" ref="listTitle" v-show="currentList.id">
<template v-if="currentList.id">
<h1
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title">
{{ currentList.title === '' ? $t('misc.loading') : getListTitle(currentList) }}
<MenuButton class="menu-button" />
<div
v-if="currentProject?.id"
class="project-title-wrapper"
>
<h1 class="project-title">
{{ currentProject.title === '' ? $t('misc.loading') : getProjectTitle(currentProject) }}
</h1>
<BaseButton :to="{name: 'list.info', params: {listId: currentList.id}}" class="info-button">
<icon icon="circle-info"/>
<BaseButton
:to="{ name: 'project.info', params: { projectId: currentProject.id } }"
class="project-title-button"
>
<icon icon="circle-info" />
</BaseButton>
<list-settings-dropdown v-if="canWriteCurrentList && currentList.id !== -1" :list="currentList"/>
<ProjectSettingsDropdown
v-if="canWriteCurrentProject && currentProject.id !== -1"
class="project-title-dropdown"
:project="currentProject"
>
<template #trigger="{ toggleOpen }">
<BaseButton
class="project-title-button"
@click="toggleOpen"
>
<icon
icon="ellipsis-h"
class="icon"
/>
</BaseButton>
</template>
</ProjectSettingsDropdown>
</div>
<div class="navbar-end">
<update/>
<OpenQuickActions />
<Notifications />
<Dropdown>
<template #trigger="{ toggleOpen, open }">
<BaseButton
@click="openQuickActions"
class="trigger-button pr-0"
v-shortcut="'Control+k'"
:title="$t('keyboardShortcuts.quickSearch')"
>
<icon icon="search"/>
</BaseButton>
<notifications/>
<div class="user">
<dropdown class="is-right" ref="usernameDropdown">
<template #trigger="{toggleOpen}">
<x-button
class="username-dropdown-trigger"
@click="toggleOpen()"
variant="secondary"
:shadow="false"
@click="toggleOpen"
>
<img
:src="authStore.avatarUrl"
alt=""
class="avatar"
width="40"
height="40"
>
<img :src="authStore.avatarUrl" alt="" class="avatar" width="40" height="40"/>
<span class="username">{{ authStore.userDisplayName }}</span>
<span class="icon is-small">
<icon icon="chevron-down"/>
<span
class="icon is-small"
:style="{
transform: open ? 'rotate(180deg)' : 'rotate(0)',
}"
>
<icon icon="chevron-down" />
</span>
</x-button>
</BaseButton>
</template>
<dropdown-item
:to="{name: 'user.settings'}"
>
<DropdownItem :to="{ name: 'user.settings' }">
{{ $t('user.settings.title') }}
</dropdown-item>
<dropdown-item
</DropdownItem>
<DropdownItem
v-if="imprintUrl"
:href="imprintUrl"
>
{{ $t('navigation.imprint') }}
</dropdown-item>
<dropdown-item
</DropdownItem>
<DropdownItem
v-if="privacyPolicyUrl"
:href="privacyPolicyUrl"
>
{{ $t('navigation.privacy') }}
</dropdown-item>
<dropdown-item
@click="baseStore.setKeyboardShortcutsActive(true)"
>
</DropdownItem>
<DropdownItem @click="baseStore.setKeyboardShortcutsActive(true)">
{{ $t('keyboardShortcuts.title') }}
</dropdown-item>
<dropdown-item
:to="{name: 'about'}"
>
</DropdownItem>
<DropdownItem :to="{ name: 'about' }">
{{ $t('about.title') }}
</dropdown-item>
<dropdown-item
@click="authStore.logout()"
>
</DropdownItem>
<DropdownItem @click="authStore.logout()">
{{ $t('user.auth.logout') }}
</dropdown-item>
</dropdown>
</div>
</DropdownItem>
</Dropdown>
</div>
</header>
</template>
<script setup lang="ts">
import {ref, computed, onMounted, nextTick} from 'vue'
import { computed } from 'vue'
import {RIGHTS as Rights} from '@/constants/rights'
import { RIGHTS as Rights } from '@/constants/rights'
import Update from '@/components/home/update.vue'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue'
import Notifications from '@/components/notifications/notifications.vue'
import Logo from '@/components/home/Logo.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import MenuButton from '@/components/home/MenuButton.vue'
import OpenQuickActions from '@/components/misc/OpenQuickActions.vue'
import {getListTitle} from '@/helpers/getListTitle'
import { getProjectTitle } from '@/helpers/getProjectTitle'
import {useBaseStore} from '@/stores/base'
import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth'
import { useBaseStore } from '@/stores/base'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth'
const baseStore = useBaseStore()
const currentList = computed(() => baseStore.currentList)
const currentProject = computed(() => baseStore.currentProject)
const background = computed(() => baseStore.background)
const canWriteCurrentList = computed(() => baseStore.currentList.maxRight > Rights.READ)
const canWriteCurrentProject = computed(() => baseStore.currentProject?.maxRight > Rights.READ)
const menuActive = computed(() => baseStore.menuActive)
const authStore = useAuthStore()
@ -121,183 +140,149 @@ const authStore = useAuthStore()
const configStore = useConfigStore()
const imprintUrl = computed(() => configStore.legal.imprintUrl)
const privacyPolicyUrl = computed(() => configStore.legal.privacyPolicyUrl)
const usernameDropdown = ref()
const listTitle = ref()
onMounted(async () => {
await nextTick()
if (typeof usernameDropdown.value === 'undefined' || typeof listTitle.value === 'undefined') {
return
}
const usernameWidth = usernameDropdown.value.$el.clientWidth
listTitle.value.style.setProperty('--nav-username-width', `${usernameWidth}px`)
})
function openQuickActions() {
baseStore.setQuickActionsActive(true)
}
</script>
<style lang="scss" scoped>
$vikunja-nav-logo-full-width: 164px;
$user-dropdown-width-mobile: 5rem;
$hamburger-menu-icon-spacing: 1rem;
$hamburger-menu-icon-width: 28px;
.navbar {
--navbar-button-min-width: 40px;
--navbar-gap-width: 1rem;
--navbar-icon-size: 1.25rem;
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
gap: var(--navbar-gap-width);
background: var(--site-background);
@media screen and (max-width: $tablet) {
padding-right: .5rem;
}
@media screen and (min-width: $tablet) {
padding-left: 2rem;
padding-right: 1rem;
align-items: stretch;
}
&.menu-active {
@media screen and (max-width: $tablet) {
z-index: 0;
}
}
// FIXME: notifications should provide a slot for the icon instead, so that we can style it as we want
:deep() {
.trigger-button {
color: var(--grey-400);
font-size: var(--navbar-icon-size);
}
}
}
.logo-link {
display: none;
padding: 0.5rem 0.75rem;
@media screen and (min-width: $tablet) {
align-self: stretch;
display: flex;
align-items: center;
padding-left: 2rem;
margin-right: 1.5rem;
margin-right: .5rem;
}
}
.menu-button {
align-self: stretch;
margin-right: auto;
align-self: stretch;
flex: 0 0 auto;
@media screen and (max-width: $tablet) {
margin-left: $hamburger-menu-icon-spacing;
margin-left: 1rem;
}
}
.navbar.main-theme {
background: var(--site-background);
justify-content: space-between;
.project-title-wrapper {
margin-inline: auto;
display: flex;
align-items: center;
@media screen and (max-width: $desktop) {
display: flex;
justify-content: space-between;
}
// this makes the truncated text of the project title work
// inside the flexbox parent
min-width: 0;
.title {
margin: 0;
font-size: 1.75rem;
@media screen and (min-width: $tablet) {
padding-inline: var(--navbar-gap-width);
}
}
.project-title {
font-size: 1rem;
// We need the following for overflowing ellipsis to work
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.navbar-end {
margin-left: 0;
align-items: center;
display: flex;
}
@media screen and (max-width: $tablet) {
&.menu-active {
z-index: 0;
}
.user {
width: $user-dropdown-width-mobile;
.username-dropdown-trigger {
line-height: 1;
padding: 0 0.25rem;
height: 1rem;
.icon {
width: .5rem;
}
}
.username {
display: none;
}
}
@media screen and (min-width: $tablet) {
font-size: 1.75rem;
}
}
.navbar {
// FIXME: notifications should provide a slot for the icon instead, so that we can style it as we want
:deep() {
.trigger-button {
cursor: pointer;
color: var(--grey-400);
padding: .5rem;
font-size: 1.25rem;
position: relative;
}
.project-title-dropdown {
align-self: stretch;
> * > .trigger-button {
width: $navbar-icon-width;
}
.project-title-button {
flex-grow: 1;
}
}
.user {
.project-title-button {
align-self: stretch;
min-width: var(--navbar-button-min-width);
display: flex;
align-items: center;
place-items: center;
justify-content: center;
font-size: var(--navbar-icon-size);
color: var(--grey-400);
}
span {
font-family: $vikunja-font;
.navbar-end {
margin-left: auto;
flex: 0 0 auto;
display: flex;
align-items: stretch;
>* {
min-width: var(--navbar-button-min-width);
}
}
.avatar {
.username-dropdown-trigger {
padding-left: 1rem;
display: inline-flex;
align-items: center;
text-transform: uppercase;
font-size: .85rem;
font-weight: 700;
}
.username {
font-family: $vikunja-font;
@media screen and (max-width: $tablet) {
display: none;
}
}
.avatar {
border-radius: 100%;
vertical-align: middle;
height: 40px;
margin-right: .5rem;
}
.username-dropdown-trigger {
background: none;
&:focus:not(:active), &:active {
outline: none !important;
box-shadow: none !important;
}
}
}
}
.list-title {
display: flex;
align-items: center;
justify-content: center;
$edit-icon-width: 1rem;
@media screen and (min-width: $tablet) {
// We need a fixed width for overflowing ellipsis to work
--nav-username-width: 0;
width: calc(100vw - #{$user-dropdown-width-mobile} - #{2 * $hamburger-menu-icon-spacing} - #{$hamburger-menu-icon-width} - #{$edit-icon-width} - #{2 * $navbar-icon-width} - #{$vikunja-nav-logo-full-width} - var(--nav-username-width));
}
@media screen and (max-width: $tablet) {
// We need a fixed width for overflowing ellipsis to work
width: calc(100vw - #{$user-dropdown-width-mobile} - #{2 * $hamburger-menu-icon-spacing} - #{$hamburger-menu-icon-width} - #{$edit-icon-width} - #{2 * $navbar-icon-width});
}
h1 {
margin: 0;
}
:deep(.dropdown-trigger) {
color: var(--grey-400);
margin-left: .5rem;
height: 1rem;
width: 1rem;
cursor: pointer;
}
}
.info-button {
text-align: center;
height: 1.25rem;
line-height: 1.25rem;
width: 2rem;
margin-top: .25rem;
padding: 0 .5rem;
color: var(--grey-400);
margin-left: .5rem;
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<div
v-if="updateAvailable"
class="update-notification"
>
<p class="update-notification__message">
{{ $t('update.available') }}
</p>
<x-button
:shadow="false"
:wrap="false"
@click="refreshApp()"
>
{{ $t('update.do') }}
</x-button>
</div>
</template>
<script lang="ts" setup>
import {computed, ref} from 'vue'
import {useBaseStore} from '@/stores/base'
const baseStore = useBaseStore()
const updateAvailable = computed(() => baseStore.updateAvailable)
const registration = ref(null)
const refreshing = ref(false)
document.addEventListener('swUpdated', showRefreshUI, {once: true})
navigator?.serviceWorker?.addEventListener(
'controllerchange', () => {
if (refreshing.value) return
refreshing.value = true
window.location.reload()
},
)
function showRefreshUI(e: Event) {
console.log('recieved refresh event', e)
registration.value = e.detail
baseStore.setUpdateAvailable(true)
}
function refreshApp() {
baseStore.setUpdateAvailable(false)
if (!registration.value || !registration.value.waiting) {
return
}
// Notify the service worker to actually do the update
registration.value.waiting.postMessage('skipWaiting')
}
</script>
<style lang="scss" scoped>
.update-notification {
position: fixed;
// FIXME: We should prevent usage of z-index or
// at least define it centrally
// the highest z-index of a modal is .hint-modal with 4500
z-index: 5000;
bottom: 1rem;
inset-inline: 1rem;
max-width: max-content;
margin-inline: auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: .5rem .5rem .5rem 1rem;
background: $warning;
border-radius: $radius;
font-size: .9rem;
color: hsl(220.9, 39.3%, 11%); // color copied to avoid it changing in dark mode
}
.update-notification__message {
width: 100%;
text-align: center;
}
</style>

View File

@ -2,10 +2,10 @@
<div class="content-auth">
<BaseButton
v-show="menuActive"
@click="baseStore.setMenuActive(false)"
class="menu-hide-button d-print-none"
@click="baseStore.setMenuActive(false)"
>
<icon icon="times"/>
<icon icon="times" />
</BaseButton>
<div
class="app-container"
@ -15,8 +15,9 @@
<div
:class="{'is-visible': background}"
class="app-container-background background-fade-in d-print-none"
:style="{'background-image': background && `url(${background})`}"></div>
<navigation class="d-print-none"/>
:style="{'background-image': background && `url(${background})`}"
/>
<Navigation class="d-print-none" />
<main
class="app-content"
:class="[
@ -26,33 +27,36 @@
>
<BaseButton
v-show="menuActive"
@click="baseStore.setMenuActive(false)"
class="mobile-overlay d-print-none"
@click="baseStore.setMenuActive(false)"
/>
<quick-actions/>
<QuickActions />
<router-view :route="routeWithModal" v-slot="{ Component }">
<keep-alive :include="['list.list', 'list.gantt', 'list.table', 'list.kanban']">
<component :is="Component"/>
<router-view
v-slot="{ Component }"
:route="routeWithModal"
>
<keep-alive :include="['project.list', 'project.gantt', 'project.table', 'project.kanban']">
<component :is="Component" />
</keep-alive>
</router-view>
<modal
:enabled="Boolean(currentModal)"
@close="closeModal()"
:enabled="typeof currentModal !== 'undefined'"
variant="scrolling"
class="task-detail-view-modal"
@close="closeModal()"
>
<component :is="currentModal"/>
<component :is="currentModal" />
</modal>
<BaseButton
v-shortcut="'?'"
class="keyboard-shortcuts-button d-print-none"
@click="showKeyboardShortcuts()"
v-shortcut="'?'"
>
<icon icon="keyboard"/>
<icon icon="keyboard" />
</BaseButton>
</main>
</div>
@ -69,6 +73,7 @@ import BaseButton from '@/components/base/BaseButton.vue'
import {useBaseStore} from '@/stores/base'
import {useLabelStore} from '@/stores/labels'
import {useProjectStore} from '@/stores/projects'
import {useRouteWithModal} from '@/composables/useRouteWithModal'
import {useRenewTokenOnFocus} from '@/composables/useRenewTokenOnFocus'
@ -86,30 +91,26 @@ function showKeyboardShortcuts() {
const route = useRoute()
// hide menu on mobile
watch(() => route.fullPath, () => window.innerWidth < 769 && baseStore.setMenuActive(false))
// FIXME: this is really error prone
// Reset the current list highlight in menu if the current route is not list related.
// Reset the current project highlight in menu if the current route is not project related.
watch(() => route.name as string, (routeName) => {
if (
routeName &&
(
[
'home',
'namespace.edit',
'teams.index',
'teams.edit',
'tasks.range',
'labels.index',
'migrate.start',
'migrate.wunderlist',
'namespaces.index',
'projects.index',
].includes(routeName) ||
routeName.startsWith('user.settings')
)
) {
baseStore.handleSetCurrentList({list: null})
baseStore.handleSetCurrentProject({project: null})
}
})
@ -119,6 +120,9 @@ useRenewTokenOnFocus()
const labelStore = useLabelStore()
labelStore.loadAllLabels()
const projectStore = useProjectStore()
projectStore.loadProjects()
</script>
<style lang="scss" scoped>
@ -159,6 +163,8 @@ labelStore.loadAllLabels()
z-index: 10;
position: relative;
padding: 1.5rem 0.5rem 1rem;
// TODO refactor: DRY `transition-timing-function` with `./navigation.vue`.
transition: margin-left $transition-duration;
@media screen and (max-width: $tablet) {
margin-left: 0;
@ -224,9 +230,4 @@ labelStore.loadAllLabels()
position: relative;
z-index: 1;
}
.is-touch .content-auth,
.content-auth.z-unset {
z-index: unset;
}
</style>

View File

@ -6,16 +6,20 @@
>
<div class="container has-text-centered link-share-view">
<div class="column is-10 is-offset-1">
<Logo class="logo" v-if="logoVisible"/>
<Logo
v-if="logoVisible"
class="logo"
/>
<h1
:class="{'m-0': !logoVisible}"
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title">
{{ currentList.title === '' ? $t('misc.loading') : currentList.title }}
:style="{ 'opacity': currentProject?.title === '' ? '0': '1' }"
class="title"
>
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
</h1>
<div class="box has-text-left view">
<router-view/>
<PoweredByLink/>
<router-view />
<PoweredByLink />
</div>
</div>
</div>
@ -31,7 +35,7 @@ import Logo from '@/components/home/Logo.vue'
import PoweredByLink from './PoweredByLink.vue'
const baseStore = useBaseStore()
const currentList = computed(() => baseStore.currentList)
const currentProject = computed(() => baseStore.currentProject)
const background = computed(() => baseStore.background)
const logoVisible = computed(() => baseStore.logoVisible)
</script>

View File

@ -1,291 +1,138 @@
<template>
<aside :class="{'is-active': menuActive}" class="namespace-container">
<aside
:class="{'is-active': baseStore.menuActive}"
class="menu-container"
>
<nav class="menu top-menu">
<router-link :to="{name: 'home'}" class="logo">
<Logo width="164" height="48"/>
<router-link
:to="{name: 'home'}"
class="logo"
>
<Logo
width="164"
height="48"
/>
</router-link>
<ul class="menu-list">
<menu class="menu-list other-menu-items">
<li>
<router-link :to="{ name: 'home'}" v-shortcut="'g o'">
<router-link
v-shortcut="'g o'"
:to="{ name: 'home'}"
>
<span class="menu-item-icon icon">
<icon icon="calendar"/>
<icon icon="calendar" />
</span>
{{ $t('navigation.overview') }}
</router-link>
</li>
<li>
<router-link :to="{ name: 'tasks.range'}" v-shortcut="'g u'">
<router-link
v-shortcut="'g u'"
:to="{ name: 'tasks.range'}"
>
<span class="menu-item-icon icon">
<icon :icon="['far', 'calendar-alt']"/>
<icon :icon="['far', 'calendar-alt']" />
</span>
{{ $t('navigation.upcoming') }}
</router-link>
</li>
<li>
<router-link :to="{ name: 'namespaces.index'}" v-shortcut="'g n'">
<router-link
v-shortcut="'g p'"
:to="{ name: 'projects.index'}"
>
<span class="menu-item-icon icon">
<icon icon="layer-group"/>
<icon icon="layer-group" />
</span>
{{ $t('namespace.title') }}
{{ $t('project.projects') }}
</router-link>
</li>
<li>
<router-link :to="{ name: 'labels.index'}" v-shortcut="'g a'">
<router-link
v-shortcut="'g a'"
:to="{ name: 'labels.index'}"
>
<span class="menu-item-icon icon">
<icon icon="tags"/>
<icon icon="tags" />
</span>
{{ $t('label.title') }}
</router-link>
</li>
<li>
<router-link :to="{ name: 'teams.index'}" v-shortcut="'g m'">
<router-link
v-shortcut="'g m'"
:to="{ name: 'teams.index'}"
>
<span class="menu-item-icon icon">
<icon icon="users"/>
<icon icon="users" />
</span>
{{ $t('team.title') }}
</router-link>
</li>
</ul>
</menu>
</nav>
<nav class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
<template v-for="(n, nk) in namespaces" :key="n.id">
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
<BaseButton
@click="toggleLists(n.id)"
class="menu-label"
v-tooltip="namespaceTitles[nk]"
>
<ColorBubble
v-if="n.hexColor !== ''"
:color="n.hexColor"
class="mr-1"
<Loading
v-if="projectStore.isLoading"
variant="small"
/>
<span class="name">{{ namespaceTitles[nk] }}</span>
<div
class="icon menu-item-icon is-small toggle-lists-icon pl-2"
:class="{'active': typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true}"
<template v-else>
<nav
v-if="favoriteProjects"
class="menu"
>
<icon icon="chevron-down"/>
</div>
<span class="count" :class="{'ml-2 mr-0': n.id > 0}">
({{ namespaceListsCount[nk] }})
</span>
</BaseButton>
<namespace-settings-dropdown class="menu-list-dropdown" :namespace="n" v-if="n.id > 0"/>
</div>
<!--
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
v-if="listsVisible[n.id] ?? true"
v-bind="dragOptions"
:modelValue="activeLists[nk]"
@update:modelValue="(lists) => updateActiveLists(n, lists)"
group="namespace-lists"
@start="() => drag = true"
@end="saveListPosition"
handle=".handle"
:disabled="n.id < 0 || undefined"
tag="ul"
item-key="id"
:data-namespace-id="n.id"
:data-namespace-index="nk"
:component-data="{
type: 'transition-group',
name: !drag ? 'flip-list' : null,
class: [
'menu-list can-be-hidden',
{ 'dragging-disabled': n.id < 0 }
]
}"
>
<template #item="{element: l}">
<li
class="list-menu loader-container is-loading-small"
:class="{'is-loading': listUpdating[l.id]}"
>
<BaseButton
:to="{ name: 'list.index', params: { listId: l.id} }"
class="list-menu-link"
:class="{'router-link-exact-active': currentList.id === l.id}"
>
<span class="icon menu-item-icon handle">
<icon icon="grip-lines"/>
</span>
<ColorBubble
v-if="l.hexColor !== ''"
:color="l.hexColor"
class="mr-1"
<ProjectsNavigation
:model-value="favoriteProjects"
:can-edit-order="false"
:can-collapse="false"
/>
<span class="list-menu-title">{{ getListTitle(l) }}</span>
</BaseButton>
<BaseButton
class="favorite"
:class="{'is-favorite': l.isFavorite}"
@click="listStore.toggleListFavorite(l)"
>
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>
</BaseButton>
<list-settings-dropdown class="menu-list-dropdown" :list="l" v-if="l.id > 0">
<template #trigger="{toggleOpen}">
<BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">
<icon icon="ellipsis-h" class="icon"/>
</BaseButton>
</template>
</list-settings-dropdown>
<span class="list-setting-spacer" v-else></span>
</li>
</template>
</draggable>
</template>
</nav>
<PoweredByLink/>
<nav
v-if="savedFilterProjects"
class="menu"
>
<ProjectsNavigation
:model-value="savedFilterProjects"
:can-edit-order="false"
:can-collapse="false"
/>
</nav>
<nav class="menu">
<ProjectsNavigation
:model-value="projects"
:can-edit-order="true"
:can-collapse="true"
:level="1"
/>
</nav>
</template>
<PoweredByLink />
</aside>
</template>
<script setup lang="ts">
import {ref, computed, onMounted, onBeforeMount} from 'vue'
import draggable from 'zhyswan-vuedraggable'
import type {SortableEvent} from 'sortablejs'
import {computed} from 'vue'
import BaseButton from '@/components/base/BaseButton.vue'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
import PoweredByLink from '@/components/home/PoweredByLink.vue'
import Logo from '@/components/home/Logo.vue'
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
import {getListTitle} from '@/helpers/getListTitle'
import {useEventListener} from '@vueuse/core'
import type {IList} from '@/modelTypes/IList'
import type {INamespace} from '@/modelTypes/INamespace'
import ColorBubble from '@/components/misc/colorBubble.vue'
import Loading from '@/components/misc/loading.vue'
import {useBaseStore} from '@/stores/base'
import {useListStore} from '@/stores/lists'
import {useNamespaceStore} from '@/stores/namespaces'
const drag = ref(false)
const dragOptions = {
animation: 100,
ghostClass: 'ghost',
}
import {useProjectStore} from '@/stores/projects'
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
const baseStore = useBaseStore()
const namespaceStore = useNamespaceStore()
const currentList = computed(() => baseStore.currentList)
const menuActive = computed(() => baseStore.menuActive)
const loading = computed(() => namespaceStore.isLoading)
const projectStore = useProjectStore()
const namespaces = computed(() => {
return namespaceStore.namespaces.filter(n => !n.isArchived)
})
const activeLists = computed(() => {
return namespaces.value.map(({lists}) => {
return lists?.filter(item => {
return typeof item !== 'undefined' && !item.isArchived
})
})
})
const namespaceTitles = computed(() => {
return namespaces.value.map((namespace) => getNamespaceTitle(namespace))
})
const namespaceListsCount = computed(() => {
return namespaces.value.map((_, index) => activeLists.value[index]?.length ?? 0)
})
useEventListener('resize', resize)
onMounted(() => resize())
const listStore = useListStore()
function resize() {
// Hide the menu by default on mobile
baseStore.setMenuActive(window.innerWidth >= 770)
}
function toggleLists(namespaceId: INamespace['id']) {
listsVisible.value[namespaceId] = !listsVisible.value[namespaceId]
}
const listsVisible = ref<{ [id: INamespace['id']]: boolean }>({})
// FIXME: async action will be unfinished when component mounts
onBeforeMount(async () => {
const namespaces = await namespaceStore.loadNamespaces()
namespaces.forEach(n => {
if (typeof listsVisible.value[n.id] === 'undefined') {
listsVisible.value[n.id] = true
}
})
})
function updateActiveLists(namespace: INamespace, activeLists: IList[]) {
// 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.
// To work around this, we merge the active lists with the archived ones. Doing so breaks the order
// because now all archived lists are sorted after the active ones. This is fine because they are sorted
// later when showing them anyway, and it makes the merging happening here a lot easier.
const lists = [
...activeLists,
...namespace.lists.filter(l => l.isArchived),
]
namespaceStore.setNamespaceById({
...namespace,
lists,
})
}
const listUpdating = ref<{ [id: INamespace['id']]: boolean }>({})
async function saveListPosition(e: SortableEvent) {
if (!e.newIndex && e.newIndex !== 0) return
const namespaceId = parseInt(e.to.dataset.namespaceId as string)
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string)
const listsActive = activeLists.value[newNamespaceIndex]
// If the list was dragged to the last position, Safari will report e.newIndex as the size of the listsActive
// array instead of using the position. Because the index is wrong in that case, dragging the list will fail.
// To work around that we're explicitly checking that case here and decrease the index.
const newIndex = e.newIndex === listsActive.length ? e.newIndex - 1 : e.newIndex
const list = listsActive[newIndex]
const listBefore = listsActive[newIndex - 1] ?? null
const listAfter = listsActive[newIndex + 1] ?? null
listUpdating.value[list.id] = true
const position = calculateItemPosition(
listBefore !== null ? listBefore.position : null,
listAfter !== null ? listAfter.position : null,
)
try {
// create a copy of the list in order to not violate pinia manipulation
await listStore.updateList({
...list,
position,
namespaceId,
})
} finally {
listUpdating.value[list.id] = false
}
}
const projects = computed(() => projectStore.notArchivedRootProjects)
const favoriteProjects = computed(() => projectStore.favoriteProjects)
const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
</script>
<style lang="scss" scoped>
$navbar-padding: 2rem;
$vikunja-nav-background: var(--site-background);
$vikunja-nav-color: var(--grey-700);
$vikunja-nav-selected-width: 0.4rem;
.logo {
display: block;
@ -298,10 +145,10 @@ $vikunja-nav-selected-width: 0.4rem;
}
}
.namespace-container {
background: $vikunja-nav-background;
.menu-container {
background: var(--site-background);
color: $vikunja-nav-color;
padding: 0 0 1rem;
padding: 1rem 0;
transition: transform $transition-duration ease-in;
position: fixed;
top: $navbar-height;
@ -323,117 +170,7 @@ $vikunja-nav-selected-width: 0.4rem;
}
}
// these are general menu styles
// should be in own components
.menu {
.menu-label,
.menu-list .list-menu-link,
.menu-list a {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
.color-bubble {
height: 12px;
flex: 0 0 12px;
}
}
.menu-list {
li {
height: 44px;
display: flex;
align-items: center;
&:hover {
background: var(--white);
}
.menu-list-dropdown {
opacity: 0;
transition: $transition;
}
&:hover .menu-list-dropdown {
opacity: 1;
}
}
.menu-item-icon {
color: var(--grey-400);
}
.menu-list-dropdown-trigger {
display: flex;
padding: 0.5rem;
}
.flip-list-move {
transition: transform $transition-duration;
}
.ghost {
background: var(--grey-200);
* {
opacity: 0;
}
}
a:hover {
background: transparent;
}
.list-menu-link,
li > a {
color: $vikunja-nav-color;
padding: 0.75rem .5rem 0.75rem ($navbar-padding * 1.5 - 1.75rem);
transition: all 0.2s ease;
border-radius: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
border-left: $vikunja-nav-selected-width solid transparent;
&:hover {
border-left: $vikunja-nav-selected-width solid var(--primary);
}
&.router-link-exact-active {
color: var(--primary);
border-left: $vikunja-nav-selected-width solid var(--primary);
}
.icon {
height: 1rem;
vertical-align: middle;
padding-right: 0.5rem;
}
&.router-link-exact-active .icon:not(.handle) {
color: var(--primary);
}
.handle {
opacity: 0;
transition: opacity $transition;
margin-right: .25rem;
cursor: grab;
}
&:hover .handle {
opacity: 1;
}
}
}
}
.top-menu {
margin-top: math.div($navbar-padding, 2);
.menu-list {
.top-menu .menu-list {
li {
font-weight: 600;
font-family: $vikunja-font;
@ -448,110 +185,9 @@ $vikunja-nav-selected-width: 0.4rem;
padding-bottom: .25rem;
}
}
}
}
.namespaces-lists {
.menu + .menu {
padding-top: math.div($navbar-padding, 2);
.menu-label {
font-size: 1rem;
font-weight: 700;
font-weight: bold;
font-family: $vikunja-font;
color: $vikunja-nav-color;
font-weight: 600;
min-height: 2.5rem;
padding-top: 0;
padding-left: $navbar-padding;
overflow: hidden;
margin-bottom: 0;
flex: 1 1 auto;
.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: auto;
}
.count {
color: var(--grey-500);
margin-right: .5rem;
// align brackets with number
font-feature-settings: "case";
}
}
.favorite {
margin-left: .25rem;
transition: opacity $transition, color $transition;
opacity: 0;
&:hover,
&.is-favorite {
color: var(--warning);
}
}
.favorite.is-favorite,
.list-menu:hover .favorite {
opacity: 1;
}
.list-menu-title {
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.color-bubble {
width: 14px;
height: 14px;
flex-basis: auto;
}
.is-archived {
min-width: 85px;
}
}
.namespace-title {
display: flex;
align-items: center;
justify-content: space-between;
color: $vikunja-nav-color;
padding: 0 .25rem;
.toggle-lists-icon {
svg {
transition: all $transition;
transform: rotate(90deg);
opacity: 1;
}
&.active svg {
transform: rotate(0deg);
opacity: 0;
}
}
&:hover .toggle-lists-icon svg {
opacity: 1;
}
&:not(.has-menu) .toggle-lists-icon {
padding-right: 1rem;
}
}
.list-setting-spacer {
width: 2.5rem;
flex-shrink: 0;
}
.namespaces-list.loader-container.is-loading {
min-height: calc(100vh - #{$navbar-height + 1.5rem + 1rem + 1.5rem});
}
</style>

View File

@ -1,81 +0,0 @@
<template>
<div class="update-notification" v-if="updateAvailable">
<p>{{ $t('update.available') }}</p>
<x-button @click="refreshApp()" :shadow="false" class="has-no-text-wrap">
{{ $t('update.do') }}
</x-button>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
const updateAvailable = ref(false)
const registration = ref(null)
const refreshing = ref(false)
document.addEventListener('swUpdated', showRefreshUI, {once: true})
if (navigator && navigator.serviceWorker) {
navigator.serviceWorker.addEventListener(
'controllerchange', () => {
if (refreshing.value) return
refreshing.value = true
window.location.reload()
},
)
}
function showRefreshUI(e: Event) {
console.log('recieved refresh event', e)
registration.value = e.detail
updateAvailable.value = true
}
function refreshApp() {
if (!registration.value || !registration.value.waiting) {
return
}
// Notify the service worker to actually do the update
registration.value.waiting.postMessage('skipWaiting')
}
</script>
<style lang="scss" scoped>
.update-notification {
display: flex;
align-items: center;
background: $warning;
padding: .5rem;
border-radius: $radius;
font-size: .9rem;
color: var(--grey-900);
justify-content: space-between;
position: fixed;
bottom: 1rem;
width: 450px;
left: calc(50vw - 225px);
@media screen and (max-width: $tablet) {
position: fixed;
left: 1rem;
right: 1rem;
bottom: 1rem;
width: auto;
}
p {
text-align: center;
width: 100%;
}
> * + * {
margin-left: .5rem;
}
}
.dark .update-notification {
color: var(--grey-200);
}
</style>

View File

@ -1,3 +1,5 @@
import {createAsyncComponent} from '@/helpers/createAsyncComponent'
export default createAsyncComponent(() => import('@/components/input/editor.vue'))
const TipTap = createAsyncComponent(() => import('@/components/input/editor/TipTap.vue'))
export default TipTap

View File

@ -6,19 +6,28 @@ import XButton from './button.vue'
<template>
<Story :layout="{ type: 'grid', width: '200px' }">
<Variant title="primary">
<XButton @click="logEvent('Click', $event)" variant="primary">
<XButton
variant="primary"
@click="logEvent('Click', $event)"
>
Order pizza!
</XButton>
</Variant>
<Variant title="secondary">
<XButton @click="logEvent('Click', $event)" variant="secondary">
<XButton
variant="secondary"
@click="logEvent('Click', $event)"
>
Order spaghetti!
</XButton>
</Variant>
<Variant title="tertiary">
<XButton @click="logEvent('Click', $event)" variant="tertiary">
<XButton
variant="tertiary"
@click="logEvent('Click', $event)"
>
Order tortellini!
</XButton>
</Variant>

View File

@ -1,44 +1,83 @@
<template>
<div class="color-picker-container">
<datalist :id="colorListID">
<option v-for="defaultColor in defaultColors" :key="defaultColor" :value="defaultColor" />
<option
v-for="defaultColor in defaultColors"
:key="defaultColor"
:value="defaultColor"
/>
</datalist>
<div class="picker">
<input
v-model="color"
class="picker__input"
type="color"
v-model="color"
:list="colorListID"
:class="{'is-empty': isEmpty}"
>
<svg
v-show="isEmpty"
class="picker__pattern"
viewBox="0 0 22 22"
fill="fff"
>
<pattern
id="checker"
width="11"
height="11"
patternUnits="userSpaceOnUse"
fill="FFF"
>
<rect
fill="#cccccc"
x="0"
width="5.5"
height="5.5"
y="0"
/>
<rect
fill="#cccccc"
x="5.5"
width="5.5"
height="5.5"
y="5.5"
/>
<svg class="picker__pattern" v-show="isEmpty" viewBox="0 0 22 22" fill="fff">
<pattern id="checker" width="11" height="11" patternUnits="userSpaceOnUse" fill="FFF">
<rect fill="#cccccc" x="0" width="5.5" height="5.5" y="0"></rect>
<rect fill="#cccccc" x="5.5" width="5.5" height="5.5" y="5.5"></rect>
</pattern>
<rect width="22" height="22" fill="url(#checker)"></rect>
<rect
width="22"
height="22"
fill="url(#checker)"
/>
</svg>
</div>
<x-button
<XButton
v-if="!isEmpty"
:disabled="isEmpty"
@click="reset"
class="is-small ml-2"
:shadow="false"
variant="secondary"
@click="reset"
>
{{ $t('input.resetColor') }}
</x-button>
</XButton>
</div>
</template>
<script setup lang="ts">
import {computed, ref, toRef, watch} from 'vue'
import {computed, ref, watch} from 'vue'
import {createRandomID} from '@/helpers/randomId'
import XButton from '@/components/input/button.vue'
const {
modelValue,
} = defineProps<{
modelValue: string,
}>()
const emit = defineEmits(['update:modelValue'])
const DEFAULT_COLORS = [
'#1973ff',
'#7F23FF',
@ -53,23 +92,18 @@ const lastChangeTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
const defaultColors = ref(DEFAULT_COLORS)
const colorListID = ref(createRandomID())
const props = defineProps({
modelValue: {
type: String,
required: true,
},
menuPosition: {
type: String,
default: 'top',
},
})
const emit = defineEmits(['update:modelValue'])
const modelValue = toRef(props, 'modelValue')
watch(
modelValue,
() => modelValue,
(newValue) => {
if (newValue === '' || newValue.startsWith('var(')) {
color.value = ''
return
}
if (!newValue.startsWith('#') && (newValue.length === 6 || newValue.length === 3)) {
newValue = `#${newValue}`
}
color.value = newValue
},
{immediate: true},

View File

@ -1,63 +0,0 @@
<template>
<multiselect
v-model="selectedLists"
:search-results="foundLists"
:loading="listService.loading"
:multiple="true"
:placeholder="$t('list.search')"
label="title"
@search="findLists"
/>
</template>
<script setup lang="ts">
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
import Multiselect from '@/components/input/multiselect.vue'
import type {IList} from '@/modelTypes/IList'
import ListService from '@/services/list'
import {includesById} from '@/helpers/utils'
const props = defineProps({
modelValue: {
type: Array as PropType<IList[]>,
default: () => [],
},
})
const emit = defineEmits<{
(e: 'update:modelValue', value: IList[]): void
}>()
const lists = ref<IList[]>([])
watchEffect(() => {
lists.value = props.modelValue
})
const selectedLists = computed({
get() {
return lists.value
},
set: (value) => {
lists.value = value
emit('update:modelValue', value)
},
})
const listService = shallowReactive(new ListService())
const foundLists = ref<IList[]>([])
async function findLists(query: string) {
if (query === '') {
foundLists.value = []
return
}
const response = await listService.getAll({}, {s: query}) as IList[]
// Filter selected items from the results
foundLists.value = response.filter(({id}) => !includesById(lists.value, id))
}
</script>

View File

@ -1,63 +0,0 @@
<template>
<multiselect
v-model="selectedNamespaces"
:search-results="foundNamespaces"
:loading="namespaceService.loading"
:multiple="true"
:placeholder="$t('namespace.search')"
label="namespace"
@search="findNamespaces"
/>
</template>
<script setup lang="ts">
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
import Multiselect from '@/components/input/multiselect.vue'
import type {INamespace} from '@/modelTypes/INamespace'
import NamespaceService from '@/services/namespace'
import {includesById} from '@/helpers/utils'
const props = defineProps({
modelValue: {
type: Array as PropType<INamespace[]>,
default: () => [],
},
})
const emit = defineEmits<{
(e: 'update:modelValue', value: INamespace[]): void
}>()
const namespaces = ref<INamespace[]>([])
watchEffect(() => {
namespaces.value = props.modelValue
})
const selectedNamespaces = computed({
get() {
return namespaces.value
},
set: (value) => {
namespaces.value = value
emit('update:modelValue', value)
},
})
const namespaceService = shallowReactive(new NamespaceService())
const foundNamespaces = ref<INamespace[]>([])
async function findNamespaces(query: string) {
if (query === '') {
foundNamespaces.value = []
return
}
const response = await namespaceService.getAll({}, {s: query}) as INamespace[]
// Filter selected items from the results
foundNamespaces.value = response.filter(({id}) => !includesById(namespaces.value, id))
}
</script>

View File

@ -0,0 +1,74 @@
<template>
<Multiselect
v-model="selectedProjects"
:search-results="foundProjects"
:loading="projectService.loading"
:multiple="true"
:placeholder="$t('project.search')"
label="title"
@search="findProjects"
/>
</template>
<script setup lang="ts">
import {computed, ref, shallowReactive, watchEffect, type PropType} from 'vue'
import Multiselect from '@/components/input/multiselect.vue'
import type {IProject} from '@/modelTypes/IProject'
import ProjectService from '@/services/project'
import {includesById} from '@/helpers/utils'
type ProjectFilterFunc = (p: IProject) => boolean
const props = defineProps({
modelValue: {
type: Array as PropType<IProject[]>,
default: () => [],
},
projectFilter: {
type: Function as PropType<ProjectFilterFunc>,
default: () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return (_: IProject) => true
},
},
})
const emit = defineEmits<{
(e: 'update:modelValue', value: IProject[]): void
}>()
const projects = ref<IProject[]>([])
watchEffect(() => {
projects.value = props.modelValue
})
const selectedProjects = computed({
get() {
return projects.value
},
set: (value) => {
projects.value = value
emit('update:modelValue', value)
},
})
const projectService = shallowReactive(new ProjectService())
const foundProjects = ref<IProject[]>([])
async function findProjects(query: string) {
if (query === '') {
foundProjects.value = []
return
}
const response = await projectService.getAll({}, {s: query}) as IProject[]
// Filter selected items from the results
foundProjects.value = response
.filter(({id}) => !includesById(projects.value, id))
.filter(props.projectFilter)
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<multiselect
<Multiselect
v-model="selectedUsers"
:search-results="foundUsers"
:loading="userService.loading"

View File

@ -0,0 +1,26 @@
<template>
<BaseButton class="simple-button">
<slot />
</BaseButton>
</template>
<script lang="ts" setup>
import BaseButton from '@/components/base/BaseButton.vue'
</script>
<style lang="scss" scoped>
.simple-button {
color: var(--text);
padding: .25rem .5rem;
transition: background-color $transition;
border-radius: $radius;
display: block;
margin: .1rem 0;
width: 100%;
text-align: left;
&:hover {
background: var(--white);
}
}
</style>

View File

@ -8,17 +8,23 @@
'has-no-shadow': !shadow || variant === 'tertiary',
}
]"
:style="{
'--button-white-space': wrap ? 'break-spaces' : 'nowrap',
}"
>
<template v-if="icon">
<icon
v-if="showIconOnly"
:icon="icon"
:style="{'color': iconColor !== '' ? iconColor : false}"
:style="{'color': iconColor !== '' ? iconColor : undefined}"
/>
<span class="icon is-small" v-else>
<span
v-else
class="icon is-small"
>
<icon
:icon="icon"
:style="{'color': iconColor !== '' ? iconColor : false}"
:style="{'color': iconColor !== '' ? iconColor : undefined}"
/>
</span>
</template>
@ -35,7 +41,7 @@ const BUTTON_TYPES_MAP = {
export type ButtonTypes = keyof typeof BUTTON_TYPES_MAP
export default { name: 'x-button' }
export default { name: 'XButton' }
</script>
<script setup lang="ts">
@ -44,12 +50,13 @@ import BaseButton, {type BaseButtonProps} from '@/components/base/BaseButton.vue
import type { IconProp } from '@fortawesome/fontawesome-svg-core'
// extending the props of the BaseButton
export interface ButtonProps extends BaseButtonProps {
export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps {
variant?: ButtonTypes
icon?: IconProp
iconColor?: string
loading?: boolean
shadow?: boolean
wrap?: boolean
}
const {
@ -58,6 +65,7 @@ const {
iconColor = '',
loading = false,
shadow = true,
wrap = true,
} = defineProps<ButtonProps>()
const variantClass = computed(() => BUTTON_TYPES_MAP[variant])
@ -77,7 +85,7 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
min-height: $button-height;
box-shadow: var(--shadow-sm);
display: inline-flex;
white-space: break-spaces;
white-space: var(--button-white-space);
&:hover {
box-shadow: var(--shadow-md);
@ -99,7 +107,6 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
&.is-primary.is-outlined:hover {
color: var(--white);
}
}
.is-small {

View File

@ -1,85 +1,29 @@
<template>
<div class="datepicker">
<BaseButton @click.stop="toggleDatePopup" class="show" :disabled="disabled || undefined">
<SimpleButton
class="show"
:disabled="disabled || undefined"
@click.stop="toggleDatePopup"
>
{{ date === null ? chooseDateLabel : formatDateShort(date) }}
</BaseButton>
</SimpleButton>
<CustomTransition name="fade">
<div v-if="show" class="datepicker-popup" ref="datepickerPopup">
<BaseButton
v-if="(new Date()).getHours() < 21"
class="datepicker__quick-select-date"
@click.stop="setDate('today')"
<div
v-if="show"
ref="datepickerPopup"
class="datepicker-popup"
>
<span class="icon"><icon :icon="['far', 'calendar-alt']"/></span>
<span class="text">
<span>{{ $t('input.datepicker.today') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('today') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('tomorrow')"
>
<span class="icon"><icon :icon="['far', 'sun']"/></span>
<span class="text">
<span>{{ $t('input.datepicker.tomorrow') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('tomorrow') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('nextMonday')"
>
<span class="icon"><icon icon="coffee"/></span>
<span class="text">
<span>{{ $t('input.datepicker.nextMonday') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('nextMonday') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('thisWeekend')"
>
<span class="icon"><icon icon="cocktail"/></span>
<span class="text">
<span>{{ $t('input.datepicker.thisWeekend') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('thisWeekend') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('laterThisWeek')"
>
<span class="icon"><icon icon="chess-knight"/></span>
<span class="text">
<span>{{ $t('input.datepicker.laterThisWeek') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('laterThisWeek') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('nextWeek')"
>
<span class="icon"><icon icon="forward"/></span>
<span class="text">
<span>{{ $t('input.datepicker.nextWeek') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('nextWeek') }}</span>
</span>
</BaseButton>
<flat-pickr
:config="flatPickerConfig"
class="input"
v-model="flatPickrDate"
<DatepickerInline
v-model="date"
@update:modelValue="updateData"
/>
<x-button
v-cy="'closeDatepicker'"
class="datepicker__close-button"
:shadow="false"
@click="close"
v-cy="'closeDatepicker'"
>
{{ $t('misc.confirm') }}
</x-button>
@ -89,19 +33,15 @@
</template>
<script setup lang="ts">
import {ref, onMounted, onBeforeUnmount, toRef, watch, computed, type PropType} from 'vue'
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
import {ref, onMounted, onBeforeUnmount, toRef, watch, type PropType} from 'vue'
import BaseButton from '@/components/base/BaseButton.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import DatepickerInline from '@/components/input/datepickerInline.vue'
import SimpleButton from '@/components/input/SimpleButton.vue'
import {formatDate, formatDateShort} from '@/helpers/time/formatDate'
import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
import {formatDateShort} from '@/helpers/time/formatDate'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {useAuthStore} from '@/stores/auth'
import {useI18n} from 'vue-i18n'
const props = defineProps({
@ -123,9 +63,7 @@ const props = defineProps({
},
})
const emit = defineEmits(['update:modelValue', 'close', 'close-on-change'])
const {t} = useI18n({useScope: 'global'})
const emit = defineEmits(['update:modelValue', 'close', 'closeOnChange'])
const date = ref<Date | null>()
const show = ref(false)
@ -141,37 +79,6 @@ watch(
{immediate: true},
)
const authStore = useAuthStore()
const weekStart = computed(() => authStore.settings.weekStart)
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
inline: true,
locale: {
firstDayOfWeek: weekStart.value,
},
}))
// Since flatpickr dates are strings, we need to convert them to native date objects.
// To make that work, we need a separate variable since flatpickr does not have a change event.
const flatPickrDate = computed({
set(newValue: string | Date) {
date.value = createDateFromString(newValue)
updateData()
},
get() {
if (!date.value) {
return ''
}
return formatDate(date.value, 'yyy-LL-dd H:mm')
},
})
function setDateValue(dateString: string | Date | null) {
if (dateString === null) {
date.value = null
@ -208,33 +115,10 @@ function close() {
emit('close', changed.value)
if (changed.value) {
changed.value = false
emit('close-on-change', changed.value)
emit('closeOnChange', changed.value)
}
}, 200)
}
function setDate(dateString: string) {
if (date.value === null) {
date.value = new Date()
}
const interval = calculateDayInterval(dateString)
const newDate = new Date()
newDate.setDate(newDate.getDate() + interval)
newDate.setHours(calculateNearestHours(newDate))
newDate.setMinutes(0)
newDate.setSeconds(0)
date.value = newDate
flatPickrDate.value = newDate
updateData()
}
function getWeekdayFromStringInterval(dateString: string) {
const interval = calculateDayInterval(dateString)
const newDate = new Date()
newDate.setDate(newDate.getDate() + interval)
return formatDate(newDate, 'E')
}
</script>
<style lang="scss" scoped>
@ -257,42 +141,6 @@ function getWeekdayFromStringInterval(dateString: string) {
}
}
.datepicker__quick-select-date {
display: flex;
align-items: center;
padding: 0 .5rem;
width: 100%;
height: 2.25rem;
color: var(--text);
transition: all $transition;
&:first-child {
border-radius: $radius $radius 0 0;
}
&:hover {
background: var(--grey-100);
}
.text {
width: 100%;
font-size: .85rem;
display: flex;
justify-content: space-between;
padding-right: .25rem;
.weekday {
color: var(--text-light);
text-transform: capitalize;
}
}
.icon {
width: 2rem;
text-align: center;
}
}
.datepicker__close-button {
margin: 1rem;
width: calc(100% - 2rem);

View File

@ -0,0 +1,225 @@
<template>
<BaseButton
v-if="(new Date()).getHours() < 21"
class="datepicker__quick-select-date"
@click.stop="setDate('today')"
>
<span class="icon"><icon :icon="['far', 'calendar-alt']" /></span>
<span class="text">
<span>{{ $t('input.datepicker.today') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('today') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('tomorrow')"
>
<span class="icon"><icon :icon="['far', 'sun']" /></span>
<span class="text">
<span>{{ $t('input.datepicker.tomorrow') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('tomorrow') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('nextMonday')"
>
<span class="icon"><icon icon="coffee" /></span>
<span class="text">
<span>{{ $t('input.datepicker.nextMonday') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('nextMonday') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('thisWeekend')"
>
<span class="icon"><icon icon="cocktail" /></span>
<span class="text">
<span>{{ $t('input.datepicker.thisWeekend') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('thisWeekend') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('laterThisWeek')"
>
<span class="icon"><icon icon="chess-knight" /></span>
<span class="text">
<span>{{ $t('input.datepicker.laterThisWeek') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('laterThisWeek') }}</span>
</span>
</BaseButton>
<BaseButton
class="datepicker__quick-select-date"
@click.stop="setDate('nextWeek')"
>
<span class="icon"><icon icon="forward" /></span>
<span class="text">
<span>{{ $t('input.datepicker.nextWeek') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('nextWeek') }}</span>
</span>
</BaseButton>
<div class="flatpickr-container">
<flat-pickr
v-model="flatPickrDate"
:config="flatPickerConfig"
/>
</div>
</template>
<script lang="ts" setup>
import {ref, toRef, watch, computed, type PropType} from 'vue'
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
import BaseButton from '@/components/base/BaseButton.vue'
import {formatDate} from '@/helpers/time/formatDate'
import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {useI18n} from 'vue-i18n'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const props = defineProps({
modelValue: {
type: [Date, null, String] as PropType<Date | null | string>,
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string',
default: null,
},
})
const emit = defineEmits(['update:modelValue', 'close-on-change'])
const {t} = useI18n({useScope: 'global'})
const date = ref<Date | null>()
const changed = ref(false)
const modelValue = toRef(props, 'modelValue')
watch(
modelValue,
setDateValue,
{immediate: true},
)
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
inline: true,
locale: getFlatpickrLanguage(),
}))
// Since flatpickr dates are strings, we need to convert them to native date objects.
// To make that work, we need a separate variable since flatpickr does not have a change event.
const flatPickrDate = computed({
set(newValue: string | Date | null) {
if (newValue === null) {
date.value = null
return
}
if (date.value !== null) {
const oldDate = formatDate(date.value, 'yyy-LL-dd H:mm')
if (oldDate === newValue) {
return
}
}
date.value = createDateFromString(newValue)
updateData()
},
get() {
if (!date.value) {
return ''
}
return formatDate(date.value, 'yyy-LL-dd H:mm')
},
})
function setDateValue(dateString: string | Date | null) {
if (dateString === null) {
date.value = null
return
}
date.value = createDateFromString(dateString)
}
function updateData() {
changed.value = true
emit('update:modelValue', date.value)
}
function setDate(dateString: string) {
const interval = calculateDayInterval(dateString)
const newDate = new Date()
newDate.setDate(newDate.getDate() + interval)
newDate.setHours(calculateNearestHours(newDate))
newDate.setMinutes(0)
newDate.setSeconds(0)
date.value = newDate
updateData()
}
function getWeekdayFromStringInterval(dateString: string) {
const interval = calculateDayInterval(dateString)
const newDate = new Date()
newDate.setDate(newDate.getDate() + interval)
return formatDate(newDate, 'E')
}
</script>
<style lang="scss" scoped>
.datepicker__quick-select-date {
display: flex;
align-items: center;
padding: 0 .5rem;
width: 100%;
height: 2.25rem;
color: var(--text);
transition: all $transition;
&:first-child {
border-radius: $radius $radius 0 0;
}
&:hover {
background: var(--grey-100);
}
.text {
width: 100%;
font-size: .85rem;
display: flex;
justify-content: space-between;
padding-right: .25rem;
.weekday {
color: var(--text-light);
text-transform: capitalize;
}
}
.icon {
width: 2rem;
text-align: center;
}
}
.flatpickr-container {
:deep(.flatpickr-calendar) {
margin: 0 auto 8px;
box-shadow: none;
}
:deep(.input) {
border: none;
}
}
</style>

View File

@ -1,446 +0,0 @@
<template>
<div class="editor">
<div class="clear"></div>
<vue-easymde
:configs="config"
@change="() => bubble()"
@update:modelValue="handleInput"
class="content"
v-if="isEditActive"
v-model="text"/>
<div class="preview content" v-html="preview" v-if="isPreviewActive && text !== ''">
</div>
<p class="has-text-centered has-text-grey is-italic my-5" v-if="showPreviewText">
{{ emptyText }}
<template v-if="isEditEnabled">
<ButtonLink
@click="toggleEdit"
v-shortcut="editShortcut"
class="d-print-none">
{{ $t('input.editor.edit') }}
</ButtonLink>.
</template>
</p>
<ul class="actions d-print-none" v-if="bottomActions.length > 0">
<li v-if="isEditEnabled && !showPreviewText && showSave">
<BaseButton
v-if="showEditButton"
@click="toggleEdit"
v-shortcut="editShortcut">
{{ $t('input.editor.edit') }}
</BaseButton>
<BaseButton
v-else-if="isEditActive"
@click="toggleEdit"
class="done-edit">
{{ $t('misc.save') }}
</BaseButton>
</li>
<li v-for="(action, k) in bottomActions" :key="k">
<BaseButton @click="action.action">{{ action.title }}</BaseButton>
</li>
</ul>
<template v-else-if="isEditEnabled && showSave">
<ul v-if="showEditButton" class="actions d-print-none">
<li>
<BaseButton
@click="toggleEdit"
v-shortcut="editShortcut">
{{ $t('input.editor.edit') }}
</BaseButton>
</li>
</ul>
<x-button
v-else-if="isEditActive"
@click="toggleEdit"
variant="secondary"
:shadow="false"
v-cy="'saveEditor'">
{{ $t('misc.save') }}
</x-button>
</template>
</div>
</template>
<script setup lang="ts">
import {computed, nextTick, onMounted, ref, toRefs, watch} from 'vue'
import VueEasymde from './vue-easymde.vue'
import {marked} from 'marked'
import DOMPurify from 'dompurify'
import {createEasyMDEConfig} from './editorConfig'
import AttachmentModel from '@/models/attachment'
import AttachmentService from '@/services/attachment'
import {setupMarkdownRenderer} from '@/helpers/markdownRenderer'
import {findCheckboxesInText} from '@/helpers/checklistFromText'
import {createRandomID} from '@/helpers/randomId'
import BaseButton from '@/components/base/BaseButton.vue'
import ButtonLink from '@/components/misc/ButtonLink.vue'
import type { IAttachment } from '@/modelTypes/IAttachment'
import type { ITask } from '@/modelTypes/ITask'
const props = defineProps({
modelValue: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
uploadEnabled: {
type: Boolean,
default: false,
},
uploadCallback: {
type: Function,
},
hasPreview: {
type: Boolean,
default: true,
},
previewIsDefault: {
type: Boolean,
default: true,
},
isEditEnabled: {
default: true,
},
bottomActions: {
type: Array,
default: () => [],
},
emptyText: {
type: String,
default: '',
},
showSave: {
type: Boolean,
default: false,
},
// If a key is passed the editor will go in "edit" mode when the key is pressed.
// Disabled if an empty string is passed.
editShortcut: {
type: String,
default: '',
},
})
const emit = defineEmits(['update:modelValue'])
const text = ref('')
const changeTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
const isEditActive = ref(false)
const isPreviewActive = ref(true)
const showPreviewText = computed(() => isPreviewActive.value && text.value === '' && props.emptyText !== '')
const showEditButton = computed(() => !isEditActive.value && text.value !== '')
const preview = ref('')
const attachmentService = new AttachmentService()
type CacheKey = `${ITask['id']}-${IAttachment['id']}`
const loadedAttachments = ref<{[key: CacheKey]: string}>({})
const config = ref(createEasyMDEConfig({
placeholder: props.placeholder,
uploadImage: props.uploadEnabled,
imageUploadFunction: props.uploadCallback,
}))
const checkboxId = ref(createRandomID())
const {modelValue} = toRefs(props)
watch(
modelValue,
async (value) => {
text.value = value
await nextTick()
renderPreview()
},
)
watch(
text,
(newVal, oldVal) => {
// Only bubble the new value if it actually changed, but not if the component just got mounted and the text changed from the outside.
if (oldVal === '' && text.value === modelValue.value) {
return
}
bubble()
},
)
onMounted(() => {
if (modelValue.value !== '') {
text.value = modelValue.value
}
if (props.previewIsDefault && props.hasPreview) {
nextTick(() => renderPreview())
return
}
isPreviewActive.value = false
isEditActive.value = true
})
// This gets triggered when only pasting content into the editor.
// A change event would not get generated by that, an input event does.
// Therefore, we're using this handler to catch paste events.
// But because this also gets triggered when typing into the editor, we give
// it a higher timeout to make the timouts cancel each other in that case so
// that in the end, only one change event is triggered to the outside per change.
function handleInput(val: string) {
// Don't bubble if the text is up to date
if (val === text.value) {
return
}
text.value = val
bubble(1000)
}
function bubble(timeout = 500) {
if (changeTimeout.value !== null) {
clearTimeout(changeTimeout.value)
}
changeTimeout.value = setTimeout(() => {
emit('update:modelValue', text.value)
}, timeout)
}
function replaceAt(str: string, index: number, replacement: string) {
return str.slice(0, index) + replacement + str.slice(index + replacement.length)
}
function findNthIndex(str: string, n: number) {
const checkboxes = findCheckboxesInText(str)
return checkboxes[n]
}
function renderPreview() {
setupMarkdownRenderer(checkboxId.value)
preview.value = DOMPurify.sanitize(marked(text.value), {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.
// To work around this, we modify the url after rendering it in the vue component.
// We're doing the whole thing in the next tick to ensure the image elements are available in the
// dom tree. If we're calling this right after setting this.preview it could be the images were
// not already made available.
// Some docs at https://stackoverflow.com/q/62865160/10924593
nextTick().then(async () => {
const attachmentImage = document.querySelectorAll<HTMLImageElement>('.attachment-image')
if (attachmentImage) {
Array.from(attachmentImage).forEach(async (img) => {
// The url is something like /tasks/<id>/attachments/<id>
const parts = img.dataset.src?.slice(window.API_URL.length + 1).split('/')
const taskId = Number(parts[1])
const attachmentId = Number(parts[3])
const cacheKey: CacheKey = `${taskId}-${attachmentId}`
if (typeof loadedAttachments.value[cacheKey] !== 'undefined') {
img.src = loadedAttachments.value[cacheKey]
return
}
const attachment = new AttachmentModel({taskId: taskId, id: attachmentId})
const url = await attachmentService.getBlobUrl(attachment)
img.src = url
loadedAttachments.value[cacheKey] = url
})
}
const textCheckbox = document.querySelectorAll<HTMLInputElement>(`.text-checkbox-${checkboxId.value}`)
if (textCheckbox) {
Array.from(textCheckbox).forEach(check => {
check.removeEventListener('change', handleCheckboxClick)
check.addEventListener('change', handleCheckboxClick)
check.parentElement?.classList.add('has-checkbox')
})
}
})
}
function handleCheckboxClick(e: Event) {
// Find the original markdown checkbox this is targeting
const checked = (e.target as HTMLInputElement).checked
const numMarkdownCheck = Number((e.target as HTMLInputElement).dataset.checkboxNum)
const index = findNthIndex(text.value, numMarkdownCheck)
if (index < 0 || typeof index === 'undefined') {
console.debug('no index found')
return
}
const listPrefix = text.value.substring(index, index + 1)
console.debug({index, listPrefix, checked, text: text.value})
text.value = replaceAt(text.value, index, `${listPrefix} ${checked ? '[x]' : '[ ]'} `)
bubble()
renderPreview()
}
function toggleEdit() {
if (isEditActive.value) {
isPreviewActive.value = true
isEditActive.value = false
renderPreview()
bubble(0) // save instantly
} else {
isPreviewActive.value = false
isEditActive.value = true
}
}
</script>
<style lang="scss">
@import 'codemirror/lib/codemirror.css';
@import 'highlight.js/scss/base16/equilibrium-gray-light';
.editor {
.clear {
clear: both;
}
.preview.content {
margin-bottom: .5rem;
overflow-wrap: anywhere; // Safari does not understand "break-word" so we put that first to make sure it at least is able to show it somewhat properly there.
overflow-wrap: break-word;
ul li {
input[type="checkbox"] {
margin-right: .5rem;
}
&.has-checkbox {
margin-left: -1.25rem;
list-style: none;
}
}
}
}
.CodeMirror {
padding: .5rem;
border: 1px solid var(--grey-200) !important;
background: var(--white);
&-lines pre {
margin: 0 !important;
}
&-placeholder {
color: var(--grey-400) !important;
font-style: italic;
}
&-cursor {
border-color: var(--grey-700);
}
}
.editor-preview {
padding: 0;
&-side {
padding: .5rem;
}
}
.editor-toolbar {
background: var(--grey-50);
border: 1px solid var(--grey-200);
border-bottom: none;
button {
color: var(--grey-700);
&.active {
background: var(--grey-200);
}
svg {
vertical-align: middle;
&, rect {
width: 20px;
height: 20px;
}
}
&::after {
position: absolute;
top: 24px;
margin-left: -3px;
}
&:hover {
background: var(--grey-200);
border-color: var(--grey-300);
}
}
i.separator {
border-color: var(--grey-200) !important;
}
}
pre.CodeMirror-line {
margin-bottom: 0 !important;
color: var(--grey-700) !important;
}
.cm-header {
font-family: $vikunja-font;
font-weight: 400;
}
ul.actions {
font-size: .8rem;
margin: 0;
li {
display: inline-block;
&::after {
content: '·';
padding: 0 .25rem;
}
&:last-child:after {
content: '';
}
}
&, a {
color: var(--grey-500);
&.done-edit {
color: var(--primary);
}
}
a:hover {
text-decoration: underline;
}
}
.vue-easymde.content {
margin-bottom: 0 !important;
}
</style>

View File

@ -0,0 +1,149 @@
<template>
<div class="items">
<template v-if="items.length">
<button
v-for="(item, index) in items"
:key="index"
class="item"
:class="{ 'is-selected': index === selectedIndex }"
@click="selectItem(index)"
>
<icon :icon="item.icon" />
<div class="description">
<p>{{ item.title }}</p>
<p>{{ item.description }}</p>
</div>
</button>
</template>
<div
v-else
class="item"
>
No result
</div>
</div>
</template>
<script lang="ts">
/* eslint-disable vue/component-api-style */
export default {
props: {
items: {
type: Array,
required: true,
},
command: {
type: Function,
required: true,
},
},
data() {
return {
selectedIndex: 0,
}
},
watch: {
items() {
this.selectedIndex = 0
},
},
methods: {
onKeyDown({event}) {
if (event.key === 'ArrowUp') {
this.upHandler()
return true
}
if (event.key === 'ArrowDown') {
this.downHandler()
return true
}
if (event.key === 'Enter') {
this.enterHandler()
return true
}
return false
},
upHandler() {
this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length
},
downHandler() {
this.selectedIndex = (this.selectedIndex + 1) % this.items.length
},
enterHandler() {
this.selectItem(this.selectedIndex)
},
selectItem(index) {
const item = this.items[index]
if (item) {
this.command(item)
}
},
},
}
</script>
<style lang="scss" scoped>
.items {
padding: 0.2rem;
position: relative;
border-radius: 0.5rem;
background: var(--white);
color: var(--grey-900);
overflow: hidden;
font-size: 0.9rem;
box-shadow: var(--shadow-md);
}
.item {
display: flex;
align-items: center;
margin: 0;
width: 100%;
text-align: left;
background: transparent;
border-radius: $radius;
border: 0;
padding: 0.2rem 0.4rem;
transition: background-color $transition;
&.is-selected, &:hover {
background: var(--grey-100);
cursor: pointer;
}
> svg {
box-sizing: border-box;
width: 2rem;
height: 2rem;
border: 1px solid var(--grey-300);
padding: .5rem;
margin-right: .5rem;
border-radius: $radius;
color: var(--grey-700);
}
}
.description {
display: flex;
flex-direction: column;
font-size: .9rem;
color: var(--grey-800);
p:last-child {
font-size: .75rem;
color: var(--grey-500);
}
}
</style>

View File

@ -0,0 +1,422 @@
<template>
<div class="editor-toolbar">
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.heading1')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-header']" />
<span class="icon__lower-text">1</span>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.heading2')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-header']" />
<span class="icon__lower-text">2</span>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.heading3')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-header']" />
<span class="icon__lower-text">3</span>
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.bold')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-bold']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.italic')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-italic']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.underline')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('underline') }"
@click="editor.chain().focus().toggleUnderline().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-underline']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.strikethrough')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('strike') }"
@click="editor.chain().focus().toggleStrike().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-strikethrough']" />
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.code')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('codeBlock') }"
@click="editor.chain().focus().toggleCodeBlock().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-code']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.quote')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor.chain().focus().toggleBlockquote().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-quote-right']" />
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.bulletList')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('bulletList') }"
@click="editor.chain().focus().toggleBulletList().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-list-ul']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.orderedList')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('orderedList') }"
@click="editor.chain().focus().toggleOrderedList().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-list-ol']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.taskList')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('taskList') }"
@click="editor.chain().focus().toggleTaskList().run()"
>
<span class="icon">
<icon icon="fa-list-check" />
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.image')"
class="editor-toolbar__button"
@click="openImagePicker"
>
<span class="icon">
<icon icon="fa-image" />
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.link')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('link') }"
title="set link"
@click="setLink"
>
<span class="icon">
<icon :icon="['fa', 'fa-link']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.text')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('paragraph') }"
title="paragraph"
@click="editor.chain().focus().setParagraph().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-paragraph']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.horizontalRule')"
class="editor-toolbar__button"
@click="editor.chain().focus().setHorizontalRule().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-ruler-horizontal']" />
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.undo')"
class="editor-toolbar__button"
@click="editor.chain().focus().undo().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-undo']" />
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.redo')"
class="editor-toolbar__button"
@click="editor.chain().focus().redo().run()"
>
<span class="icon">
<icon :icon="['fa', 'fa-redo']" />
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<!-- table -->
<BaseButton
v-tooltip="$t('input.editor.table.title')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('table') }"
@click="toggleTableMode"
>
<span class="icon">
<icon :icon="['fa', 'fa-table']" />
</span>
</BaseButton>
<div
v-if="tableMode"
class="editor-toolbar__table-buttons"
>
<BaseButton
class="editor-toolbar__button"
@click="
editor
.chain()
.focus()
.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
.run()
"
>
{{ $t('input.editor.table.insert') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().addColumnBefore"
@click="editor.chain().focus().addColumnBefore().run()"
>
{{ $t('input.editor.table.addColumnBefore') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().addColumnAfter"
@click="editor.chain().focus().addColumnAfter().run()"
>
{{ $t('input.editor.table.addColumnAfter') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().deleteColumn"
@click="editor.chain().focus().deleteColumn().run()"
>
{{ $t('input.editor.table.deleteColumn') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().addRowBefore"
@click="editor.chain().focus().addRowBefore().run()"
>
{{ $t('input.editor.table.addRowBefore') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().addRowAfter"
@click="editor.chain().focus().addRowAfter().run()"
>
{{ $t('input.editor.table.addRowAfter') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().deleteRow"
@click="editor.chain().focus().deleteRow().run()"
>
{{ $t('input.editor.table.deleteRow') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().deleteTable"
@click="editor.chain().focus().deleteTable().run()"
>
{{ $t('input.editor.table.deleteTable') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().mergeCells"
@click="editor.chain().focus().mergeCells().run()"
>
{{ $t('input.editor.table.mergeCells') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().splitCell"
@click="editor.chain().focus().splitCell().run()"
>
{{ $t('input.editor.table.splitCell') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().toggleHeaderColumn"
@click="editor.chain().focus().toggleHeaderColumn().run()"
>
{{ $t('input.editor.table.toggleHeaderColumn') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().toggleHeaderRow"
@click="editor.chain().focus().toggleHeaderRow().run()"
>
{{ $t('input.editor.table.toggleHeaderRow') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().toggleHeaderCell"
@click="editor.chain().focus().toggleHeaderCell().run()"
>
{{ $t('input.editor.table.toggleHeaderCell') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().mergeOrSplit"
@click="editor.chain().focus().mergeOrSplit().run()"
>
{{ $t('input.editor.table.mergeOrSplit') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().fixTables"
@click="editor.chain().focus().fixTables().run()"
>
{{ $t('input.editor.table.fixTables') }}
</BaseButton>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import {Editor} from '@tiptap/vue-3'
import BaseButton from '@/components/base/BaseButton.vue'
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
const {
editor = null,
} = defineProps<{
editor: Editor,
}>()
const tableMode = ref(false)
function toggleTableMode() {
tableMode.value = !tableMode.value
}
function openImagePicker() {
document.getElementById('tiptap__image-upload').click()
}
function setLink(event) {
setLinkInEditor(event.target.getBoundingClientRect(), editor)
}
</script>
<style lang="scss" scoped>
.editor-toolbar {
background: var(--white);
border: 1px solid var(--grey-200);
user-select: none;
padding: .5rem;
border-radius: $radius;
display: flex;
flex-wrap: wrap;
> * + * {
border-left: 1px solid var(--grey-200);
margin-left: 6px;
padding-left: 6px;
}
}
.editor-toolbar__button {
min-width: 2rem;
height: 2rem;
border-radius: $radius;
border: 1px solid transparent;
color: var(--grey-700);
transition: all $transition;
background: transparent;
margin-right: .25rem;
&:hover {
background: var(--grey-100);
border-color: var(--grey-200);
}
.icon {
position: relative;
.icon__lower-text {
font-size: .75rem;
position: absolute;
bottom: -3px;
right: -2px;
font-weight: bold;
}
}
}
.editor-toolbar__table-buttons {
margin-top: .5rem;
> .editor-toolbar__button {
margin-right: .5rem;
margin-bottom: .5rem;
padding: 0 .25rem;
border: 1px solid var(--grey-400);
font-size: .75rem;
height: 1.5rem;
}
}
</style>

View File

@ -0,0 +1,945 @@
<template>
<div
ref="tiptapInstanceRef"
class="tiptap"
>
<EditorToolbar
v-if="editor && isEditing"
:editor="editor"
:upload-callback="uploadCallback"
/>
<BubbleMenu
v-if="editor && isEditing"
:editor="editor"
class="editor-bubble__wrapper"
>
<BaseButton
v-tooltip="$t('input.editor.bold')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
>
<icon :icon="['fa', 'fa-bold']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.italic')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
>
<icon :icon="['fa', 'fa-italic']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.underline')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('underline') }"
@click="editor.chain().focus().toggleUnderline().run()"
>
<icon :icon="['fa', 'fa-underline']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.strikethrough')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('strike') }"
@click="editor.chain().focus().toggleStrike().run()"
>
<icon :icon="['fa', 'fa-strikethrough']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.code')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('code') }"
@click="editor.chain().focus().toggleCode().run()"
>
<icon :icon="['fa', 'fa-code']" />
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.link')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('link') }"
@click="setLink"
>
<icon :icon="['fa', 'fa-link']" />
</BaseButton>
</BubbleMenu>
<EditorContent
class="tiptap__editor"
:class="{'tiptap__editor-is-edit-enabled': isEditing}"
:editor="editor"
@click="focusIfEditing()"
/>
<input
v-if="isEditing"
id="tiptap__image-upload"
ref="uploadInputRef"
type="file"
class="is-hidden"
@change="addImage"
>
<ul
v-if="bottomActions.length === 0 && !isEditing && isEditEnabled"
class="tiptap__editor-actions d-print-none"
>
<li>
<BaseButton
class="done-edit"
@click="setEdit"
>
{{ $t('input.editor.edit') }}
</BaseButton>
</li>
</ul>
<ul
v-if="bottomActions.length > 0"
class="tiptap__editor-actions d-print-none"
>
<li v-if="isEditing && showSave">
<BaseButton
class="done-edit"
@click="bubbleSave"
>
{{ $t('misc.save') }}
</BaseButton>
</li>
<li v-if="!isEditing">
<BaseButton
class="done-edit"
@click="setEdit"
>
{{ $t('input.editor.edit') }}
</BaseButton>
</li>
<li
v-for="(action, k) in bottomActions"
:key="k"
>
<BaseButton @click="action.action">
{{ action.title }}
</BaseButton>
</li>
</ul>
<XButton
v-else-if="isEditing && showSave"
v-cy="'saveEditor'"
class="mt-4"
variant="secondary"
:shadow="false"
:disabled="!contentHasChanged"
@click="bubbleSave"
>
{{ $t('misc.save') }}
</XButton>
</div>
</template>
<script setup lang="ts">
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue'
import EditorToolbar from './EditorToolbar.vue'
import Link from '@tiptap/extension-link'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import Table from '@tiptap/extension-table'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import TableRow from '@tiptap/extension-table-row'
import Typography from '@tiptap/extension-typography'
import Image from '@tiptap/extension-image'
import Underline from '@tiptap/extension-underline'
import TaskItem from '@tiptap/extension-task-item'
import TaskList from '@tiptap/extension-task-list'
import {Blockquote} from '@tiptap/extension-blockquote'
import {Bold} from '@tiptap/extension-bold'
import {BulletList} from '@tiptap/extension-bullet-list'
import {Code} from '@tiptap/extension-code'
import {Document} from '@tiptap/extension-document'
import {Dropcursor} from '@tiptap/extension-dropcursor'
import {Gapcursor} from '@tiptap/extension-gapcursor'
import {HardBreak} from '@tiptap/extension-hard-break'
import {Heading} from '@tiptap/extension-heading'
import {History} from '@tiptap/extension-history'
import {HorizontalRule} from '@tiptap/extension-horizontal-rule'
import {Italic} from '@tiptap/extension-italic'
import {ListItem} from '@tiptap/extension-list-item'
import {OrderedList} from '@tiptap/extension-ordered-list'
import {Paragraph} from '@tiptap/extension-paragraph'
import {Strike} from '@tiptap/extension-strike'
import {Text} from '@tiptap/extension-text'
import {BubbleMenu, EditorContent, useEditor} from '@tiptap/vue-3'
import {Node} from '@tiptap/pm/model'
import Commands from './commands'
import suggestionSetup from './suggestion'
import {lowlight} from 'lowlight'
import type {BottomAction, UploadCallback} from './types'
import type {ITask} from '@/modelTypes/ITask'
import type {IAttachment} from '@/modelTypes/IAttachment'
import AttachmentModel from '@/models/attachment'
import AttachmentService from '@/services/attachment'
import {useI18n} from 'vue-i18n'
import BaseButton from '@/components/base/BaseButton.vue'
import XButton from '@/components/input/button.vue'
import {Placeholder} from '@tiptap/extension-placeholder'
import {eventToHotkeyString} from '@github/hotkey'
import {mergeAttributes} from '@tiptap/core'
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
import inputPrompt from '@/helpers/inputPrompt'
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
const {
modelValue,
uploadCallback,
isEditEnabled = true,
bottomActions = [],
showSave = false,
placeholder = '',
editShortcut = '',
} = defineProps<{
modelValue: string,
uploadCallback?: UploadCallback,
isEditEnabled?: boolean,
bottomActions?: BottomAction[],
showSave?: boolean,
placeholder?: string,
editShortcut?: string,
}>()
const emit = defineEmits(['update:modelValue', 'save'])
const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
const {t} = useI18n()
const CustomTableCell = TableCell.extend({
addAttributes() {
return {
// extend the existing attributes
...this.parent?.(),
// and add a new one
backgroundColor: {
default: null,
parseHTML: (element: HTMLElement) => element.getAttribute('data-background-color'),
renderHTML: (attributes) => {
return {
'data-background-color': attributes.backgroundColor,
style: `background-color: ${attributes.backgroundColor}`,
}
},
},
}
},
})
type CacheKey = `${ITask['id']}-${IAttachment['id']}`
const loadedAttachments = ref<{
[key: CacheKey]: string
}>({})
const CustomImage = Image.extend({
addAttributes() {
return {
src: {
default: null,
},
alt: {
default: null,
},
title: {
default: null,
},
id: {
default: null,
},
'data-src': {
default: null,
},
}
},
renderHTML({HTMLAttributes}) {
if (HTMLAttributes.src?.startsWith(window.API_URL) || HTMLAttributes['data-src']?.startsWith(window.API_URL)) {
const imageUrl = HTMLAttributes['data-src'] ?? HTMLAttributes.src
// The url is something like /tasks/<id>/attachments/<id>
const parts = imageUrl.slice(window.API_URL.length + 1).split('/')
const taskId = Number(parts[1])
const attachmentId = Number(parts[3])
const cacheKey: CacheKey = `${taskId}-${attachmentId}`
const id = 'tiptap-image-' + cacheKey
nextTick(async () => {
const img = document.getElementById(id)
if (!img) return
if (typeof loadedAttachments.value[cacheKey] === 'undefined') {
const attachment = new AttachmentModel({taskId: taskId, id: attachmentId})
const attachmentService = new AttachmentService()
loadedAttachments.value[cacheKey] = await attachmentService.getBlobUrl(attachment)
}
img.src = loadedAttachments.value[cacheKey]
})
return ['img', mergeAttributes(this.options.HTMLAttributes, {
'data-src': imageUrl,
src: '#',
alt: HTMLAttributes.alt,
title: HTMLAttributes.title,
id,
})]
}
return ['img', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]
},
})
type Mode = 'edit' | 'preview'
const internalMode = ref<Mode>('preview')
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled)
const contentHasChanged = ref<boolean>(false)
watch(
() => internalMode.value,
mode => {
if (mode === 'preview') {
contentHasChanged.value = false
}
},
)
const editor = useEditor({
// eslint-disable-next-line vue/no-ref-object-destructure
editable: isEditing.value,
extensions: [
// Starterkit:
Blockquote,
Bold,
BulletList,
Code,
CodeBlockLowlight.configure({
lowlight,
}),
Document,
Dropcursor,
Gapcursor,
HardBreak.extend({
addKeyboardShortcuts() {
return {
'Mod-Enter': () => {
if (contentHasChanged.value) {
bubbleSave()
}
},
}
},
}),
Heading,
History,
HorizontalRule,
Italic,
ListItem,
OrderedList,
Paragraph,
Strike,
Text,
Placeholder.configure({
placeholder: ({editor}) => {
if (!isEditing.value) {
return ''
}
if (editor.getText() !== '' && !editor.isFocused) {
return ''
}
return placeholder !== ''
? placeholder
: t('input.editor.placeholder')
},
}),
Typography,
Underline,
Link.configure({
openOnClick: true,
validate: (href: string) => /^https?:\/\//.test(href),
}),
Table.configure({
resizable: true,
}),
TableRow,
TableHeader,
// Custom TableCell with backgroundColor attribute
CustomTableCell,
CustomImage,
TaskList,
TaskItem.configure({
nested: true,
onReadOnlyChecked: (node: Node, checked: boolean): boolean => {
if (!isEditEnabled) {
return false
}
// The following is a workaround for this bug:
// https://github.com/ueberdosis/tiptap/issues/4521
// https://github.com/ueberdosis/tiptap/issues/3676
editor.value!.state.doc.descendants((subnode, pos) => {
if (node.eq(subnode)) {
const {tr} = editor.value!.state
tr.setNodeMarkup(pos, undefined, {
...node.attrs,
checked,
})
editor.value!.view.dispatch(tr)
bubbleSave()
}
})
return true
},
}),
Commands.configure({
suggestion: suggestionSetup(t),
}),
BubbleMenu,
],
onUpdate: () => {
bubbleNow()
},
})
watch(
() => isEditing.value,
() => {
editor.value?.setEditable(isEditing.value)
},
{immediate: true},
)
watch(
() => modelValue,
value => {
if (!editor?.value) return
if (editor.value.getHTML() === value) {
return
}
setModeAndValue(value)
},
{immediate: true},
)
function bubbleNow() {
if (editor.value?.getHTML() === modelValue) {
return
}
contentHasChanged.value = true
emit('update:modelValue', editor.value?.getHTML())
}
function bubbleSave() {
bubbleNow()
emit('save', editor.value?.getHTML())
if (isEditing.value) {
internalMode.value = 'preview'
}
}
function setEdit(focus: boolean = true) {
internalMode.value = 'edit'
if (focus) {
editor.value?.commands.focus()
}
}
onBeforeUnmount(() => editor.value?.destroy())
const uploadInputRef = ref<HTMLInputElement | null>(null)
function uploadAndInsertFiles(files: File[] | FileList) {
uploadCallback(files).then(urls => {
urls?.forEach(url => {
editor.value
?.chain()
.focus()
.setImage({src: url})
.run()
})
bubbleSave()
})
}
async function addImage(event) {
if (typeof uploadCallback !== 'undefined') {
const files = uploadInputRef.value?.files
if (!files || files.length === 0) {
return
}
uploadAndInsertFiles(files)
return
}
const url = await inputPrompt(event.target.getBoundingClientRect())
if (url) {
editor.value?.chain().focus().setImage({src: url}).run()
bubbleSave()
}
}
function setLink(event) {
setLinkInEditor(event.target.getBoundingClientRect(), editor.value)
}
onMounted(async () => {
if (editShortcut !== '') {
document.addEventListener('keydown', setFocusToEditor)
}
await nextTick()
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
input?.addEventListener('paste', handleImagePaste)
setModeAndValue(modelValue)
})
onBeforeUnmount(() => {
nextTick(() => {
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
input?.removeEventListener('paste', handleImagePaste)
})
if (editShortcut !== '') {
document.removeEventListener('keydown', setFocusToEditor)
}
})
function setModeAndValue(value: string) {
internalMode.value = isEditorContentEmpty(value) ? 'edit' : 'preview'
editor.value?.commands.setContent(value, false)
}
function handleImagePaste(event) {
if (event?.clipboardData?.items?.length === 0) {
return
}
event.preventDefault()
const image = event.clipboardData.items[0]
if (image.kind === 'file' && image.type.startsWith('image/')) {
uploadAndInsertFiles([image.getAsFile()])
}
}
// See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660
function setFocusToEditor(event) {
const hotkeyString = eventToHotkeyString(event)
if (!hotkeyString) return
if (hotkeyString !== editShortcut ||
event.target.tagName.toLowerCase() === 'input' ||
event.target.tagName.toLowerCase() === 'textarea' ||
event.target.contentEditable === 'true') {
return
}
event.preventDefault()
if (!isEditing.value && isEditEnabled) {
internalMode.value = 'edit'
}
editor.value?.commands.focus()
}
function focusIfEditing() {
if (isEditing.value) {
editor.value?.commands.focus()
}
}
function clickTasklistCheckbox(event) {
event.stopImmediatePropagation()
if (event.target.localName !== 'p') {
return
}
event.target.parentNode.parentNode.firstChild.click()
}
watch(
() => isEditing.value,
editing => {
nextTick(() => {
const checkboxes = tiptapInstanceRef.value?.querySelectorAll('[data-checked]')
if (typeof checkboxes === 'undefined' || checkboxes.length === 0) {
return
}
if (editing) {
checkboxes.forEach(check => {
if (check.children.length < 2) {
return
}
// We assume the first child contains the label element with the checkbox and the second child the actual label
// When the actual label is clicked, we forward that click to the checkbox.
check.children[1].removeEventListener('click', clickTasklistCheckbox)
})
return
}
checkboxes.forEach(check => {
if (check.children.length < 2) {
return
}
// We assume the first child contains the label element with the checkbox and the second child the actual label
// When the actual label is clicked, we forward that click to the checkbox.
check.children[1].addEventListener('click', clickTasklistCheckbox)
})
})
},
{immediate: true},
)
</script>
<style lang="scss">
.tiptap__editor {
&.tiptap__editor-is-edit-enabled {
min-height: 10rem;
.ProseMirror {
padding: .5rem;
}
&:focus-within, &:focus {
box-shadow: 0 0 0 2px hsla(var(--primary-hsl), 0.5);
}
ul[data-type='taskList'] li > div {
cursor: text;
}
}
transition: box-shadow $transition;
border-radius: $radius;
}
.tiptap p::before {
content: attr(data-placeholder);
color: var(--grey-400);
pointer-events: none;
height: 0;
float: left;
}
// Basic editor styles
.ProseMirror {
padding: .5rem .5rem .5rem 0;
&:focus-within, &:focus {
box-shadow: none;
}
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
code {
background-color: var(--grey-200);
color: var(--grey-700);
}
pre {
background: var(--grey-200);
color: var(--grey-700);
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: $radius;
code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
.hljs-comment,
.hljs-quote {
color: var(--grey-500);
}
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-name,
.hljs-regexp,
.hljs-link,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: var(--code-variable);
}
.hljs-number,
.hljs-meta,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: var(--code-literal);
}
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: var(--code-symbol);
}
.hljs-title,
.hljs-section {
color: var(--code-section);
}
.hljs-keyword,
.hljs-selector-tag {
color: var(--code-keyword);
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: 700;
}
}
img {
max-width: 100%;
height: auto;
&.ProseMirror-selectednode {
outline: 3px solid var(--primary);
}
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0d0d0d, 0.1);
}
hr {
border: none;
border-top: 2px solid rgba(#0d0d0d, 0.1);
margin: 2rem 0;
}
}
.ProseMirror {
/* Table-specific styling */
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 0;
overflow: hidden;
td,
th {
min-width: 1em;
border: 2px solid #ced4da;
padding: 3px 5px;
vertical-align: top;
box-sizing: border-box;
position: relative;
> * {
margin-bottom: 0;
}
}
th {
font-weight: bold;
text-align: left;
background-color: #f1f3f5;
}
.selectedCell:after {
z-index: 2;
position: absolute;
content: '';
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(200, 200, 255, 0.4);
pointer-events: none;
}
.column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: -2px;
width: 4px;
background-color: #adf;
pointer-events: none;
}
p {
margin: 0;
}
}
// Lists
ul {
margin-left: .5rem;
margin-top: 0 !important;
li {
margin-top: 0;
}
p {
margin-bottom: 0 !important;
}
}
}
.tableWrapper {
overflow-x: auto;
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
// tasklist
ul[data-type='taskList'] {
list-style: none;
padding: 0;
margin-left: 0;
li {
display: flex;
> label {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
}
> div {
flex: 1 1 auto;
cursor: pointer;
}
}
input[type='checkbox'] {
cursor: pointer;
}
}
.editor-bubble__wrapper {
background: var(--white);
border-radius: $radius;
border: 1px solid var(--grey-200);
box-shadow: var(--shadow-md);
display: flex;
overflow: hidden;
}
.editor-bubble__button {
color: var(--grey-700);
transition: all $transition;
background: transparent;
svg {
box-sizing: border-box;
display: block;
width: 1rem;
height: 1rem;
padding: .5rem;
margin: 0;
}
&:hover {
background: var(--grey-200);
}
}
ul.tiptap__editor-actions {
font-size: .8rem;
margin: 0;
li {
display: inline-block;
&::after {
content: '·';
padding: 0 .25rem;
}
&:last-child:after {
content: '';
}
}
&, a {
color: var(--grey-500);
&.done-edit {
color: var(--primary);
}
}
a:hover {
text-decoration: underline;
}
}
</style>

View File

@ -0,0 +1,28 @@
import {Extension} from '@tiptap/core'
import Suggestion from '@tiptap/suggestion'
// Copied and adjusted from https://github.com/ueberdosis/tiptap/tree/252acb32d27a0f9af14813eeed83d8a50059a43a/demos/src/Experiments/Commands/Vue
export default Extension.create({
name: 'slash-menu-commands',
addOptions() {
return {
suggestion: {
char: '/',
command: ({editor, range, props}) => {
props.command({editor, range})
},
},
}
},
addProseMirrorPlugins() {
return [
Suggestion({
editor: this.editor,
...this.options.suggestion,
}),
]
},
})

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