Compare commits

...

544 Commits

Author SHA1 Message Date
kolaente 652d3c7384
chore: add archival note
continuous-integration/drone/push Build is passing Details
2024-02-07 15:03:24 +01:00
kolaente 447641c222 chore: apply lint fixes
continuous-integration/drone/push Build is passing Details
2024-02-07 12:23:09 +00:00
Dominik Pschenitschni 362be53a47 feat: use recommended vue-linting 2024-02-07 12:23:09 +00:00
renovate 46eabdfe6b fix(deps): update sentry-javascript monorepo to v7.100.1
continuous-integration/drone/push Build is passing Details
2024-02-07 11:53:00 +00:00
kolaente a0c5a464a5
feat(progress): less rounding
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-02-07 11:36:57 +01:00
kolaente e78ab476fc
chore(progress): cleanup unused css 2024-02-07 11:35:32 +01:00
kolaente aebb047d18
fix(progress): move customizations into progress bar component
continuous-integration/drone/pr Build is passing Details
2024-02-07 11:24:20 +01:00
Dominik Pschenitschni 7bb110b20e
feat: add ProgressBar component
continuous-integration/drone/pr Build is passing Details
2024-02-07 11:12:21 +01:00
renovate f148a43390 fix(deps): update dependency ufo to v1.4.0
continuous-integration/drone/push Build is passing Details
2024-02-07 10:08:12 +00:00
renovate aac70d3823 fix(deps): update dependency @kyvg/vue3-notification to v3.1.4
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-02-07 09:20:36 +00:00
kolaente 21126793ab
fix(test): make test assertion work again
continuous-integration/drone/push Build is passing Details
2024-02-06 23:13:38 +01:00
kolaente b057fb2784
fix(reminders): set reminder date on datepicker when editing a reminder
continuous-integration/drone/push Build is failing Details
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
kolaente 58c7da019d
fix(notifications): mark all notifications as read in ui directly when marking as read on the server
continuous-integration/drone/push Build is failing Details
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
kolaente 70f48eaaca
fix(task): make sure the drag handle is shown as intended
continuous-integration/drone/push Build is failing Details
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 #3934
2024-02-06 18:29:17 +01:00
kolaente 6cc75928d8
fix(task): remove default task color
continuous-integration/drone/push Build is failing Details
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
kolaente dc360d4a18
chore(editor): don't set editor content intitially
continuous-integration/drone/push Build is failing Details
2024-02-06 18:03:27 +01:00
kolaente 45ca0602f5
feat(editor): use primary color for currently selected node 2024-02-06 16:09:38 +01:00
kolaente 9d39ccf15c
fix(assignees): use correct amount of spacing in assignee selection
continuous-integration/drone/push Build is failing Details
2024-02-06 15:44:39 +01:00
kolaente 28e83325d7
fix(kanban): assignee spacing 2024-02-06 15:39:05 +01:00
kolaente aff48ddd9d
fix(kanban): bottom spacing of labels 2024-02-06 15:34:22 +01:00
kolaente 5b2a9a42c0
fix(gantt): correctly import languages from dayjs
continuous-integration/drone/push Build is failing Details
Resolves https://community.vikunja.io/t/error-in-gannt-with-spanish-language/1973/3
2024-02-05 21:57:21 +01:00
kolaente 45f5d522d1
docs: update readme
continuous-integration/drone/push Build is failing Details
Copied from https://github.com/go-vikunja/frontend/pull/146
2024-02-05 21:16:30 +01:00
renovate 4f27e4a477 fix(deps): update tiptap to v2.2.1 2024-02-05 10:37:28 +00:00
renovate d0dc86fd58 fix(deps): update dependency vue-i18n to v9.9.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-31 02:20:29 +00:00
renovate 0484923b8a fix(deps): update sentry-javascript monorepo to v7.99.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-30 17:18:57 +00:00
renovate 5f2fb01e90 fix(deps): update dependency floating-vue to v5.2.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-30 14:19:37 +00:00
renovate bd18524f36 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-30 00:19:43 +00:00
renovate 7375a87f2f fix(deps): update dependency @fortawesome/vue-fontawesome to v3.0.6
continuous-integration/drone/push Build is passing Details
2024-01-29 21:18:09 +00:00
renovate ccff276397 chore(deps): update pnpm to v8.15.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-29 20:19:25 +00:00
renovate 30b21fc11c fix(deps): update dependency floating-vue to v5.2.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-29 14:22:11 +00:00
renovate 7c98ddc20b fix(deps): update tiptap to v2.2.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-29 13:21:38 +00:00
renovate 6ba02a0f10 chore(deps): update pnpm to v8.15.0
continuous-integration/drone/push Build is passing Details
2024-01-29 07:32:55 +00:00
renovate 676d2b6215 chore(deps): update dependency @types/node to v20.11.10
continuous-integration/drone/push Build is passing Details
2024-01-29 07:32:26 +00:00
Frederick [Bot] 85e612451f chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-01-29 00:25:58 +00:00
kolaente d411de99f1
chore: release preparation
continuous-integration/drone/push Build is passing Details
2024-01-28 17:43:53 +01:00
kolaente 228d652b03
fix(kanban): make sure spacing between assignees and other task details works out evenly
continuous-integration/drone/push Build is passing Details
2024-01-28 16:41:24 +01:00
kolaente b3e2107503
fix(task): don't show assignee edit buttons and input when the user does not have the permission to edit
continuous-integration/drone/push Build is passing Details
2024-01-28 13:30:29 +01:00
kolaente a579a8e65f
fix(task): don't show edit button when the user does not have permission to edit the task 2024-01-28 13:24:58 +01:00
kolaente ee980e2a00
fix(openid): use the calculated redirect url when authenticating with openid providers
continuous-integration/drone/push Build is passing Details
Resolves https://github.com/go-vikunja/desktop/issues/12
2024-01-28 12:42:45 +01:00
renovate 394dbe0055 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-28 04:19:06 +00:00
renovate 30d599369f chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-27 00:18:54 +00:00
kolaente 631b02d2ee
chore: only show webhooks overview table when there are webhooks
continuous-integration/drone/push Build is passing Details
2024-01-27 00:01:11 +01:00
kolaente 326bfb557a
chore: only show webhooks overview table when there are webhooks
continuous-integration/drone/push Build is passing Details
2024-01-27 00:00:31 +01:00
kolaente cd0149ef69
fix(kanban): make sure the checklist summary uses the correct text color
continuous-integration/drone/push Build is passing Details
Related-To https://github.com/go-vikunja/frontend/issues/135
2024-01-26 21:44:20 +01:00
kolaente 78d4a518a3
fix(tasks): don't load tasks multiple times when viewing list or gantt view
continuous-integration/drone/push Build is passing Details
2024-01-26 21:33:20 +01:00
kolaente 3c1041902e
fix(table view): make sure popup does not overlap
continuous-integration/drone/push Build is passing Details
2024-01-26 21:22:51 +01:00
kolaente e3cae0ed7f
fix(filter): validate filter title field after loading a filter for edit
continuous-integration/drone/push Build is passing Details
Related to #3866
2024-01-26 11:29:46 +01:00
renovate fc8bd6a9ca chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-26 05:19:03 +00:00
renovate 5a6e5619e3 fix(deps): update dependency axios to v1.6.7
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-25 20:20:22 +00:00
renovate 9c9f806e62 fix(deps): update sentry-javascript monorepo to v7.98.0
continuous-integration/drone/push Build is passing Details
2024-01-25 13:55:42 +00:00
kolaente 67216579bc
fix(auth): correctly construct redirect url from current window href
continuous-integration/drone/push Build is passing Details
2024-01-25 14:24:30 +01:00
renovate a8df935ddb fix(deps): update sentry-javascript monorepo to v7.97.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-25 11:20:20 +00:00
renovate bb4746f226 chore(deps): update dev-dependencies
continuous-integration/drone/push Build is passing Details
2024-01-25 07:50:16 +00:00
renovate 31590236aa fix(deps): update dependency axios to v1.6.6
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-24 23:19:33 +00:00
renovate 00d48a6178 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-24 06:19:41 +00:00
renovate 5169cca8d8 fix(deps): update sentry-javascript monorepo to v7.95.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-23 19:18:41 +00:00
renovate 255a7d565c chore(deps): update pnpm to v8.14.3
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-23 10:20:44 +00:00
renovate 8dbaee5dfb chore(deps): update dev-dependencies to v6.19.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-23 00:19:30 +00:00
renovate 69b0b19482 fix(deps): update dependency date-fns to v3.3.1
continuous-integration/drone/push Build is passing Details
2024-01-22 10:44:49 +00:00
renovate eae89d37f1 chore(deps): update pnpm to v8.14.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-22 10:19:22 +00:00
renovate 7d19859816 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-22 00:18:43 +00:00
kolaente c7b70844c6
fix(color picker): when picking a color, the color picker should not be black afterwards
continuous-integration/drone/push Build is passing Details
2024-01-21 20:25:19 +01:00
kolaente b8c21c2ade
fix(labels): text and background combination in dark mode
continuous-integration/drone/push Build is passing Details
2024-01-21 20:20:00 +01:00
kolaente 57c99a22a0
fix(editor): use manual input prompt instead of window.prompt
continuous-integration/drone/push Build is passing Details
Resolves vikunja/desktop#184
2024-01-21 20:08:10 +01:00
kolaente 8ea97f3ffc
fix(editor): use a stable image id to prevent constant re-rendering
continuous-integration/drone/push Build is passing Details
2024-01-21 15:45:18 +01:00
kolaente 0b3604d167
fix(editor): render images without crashing
continuous-integration/drone/push Build is passing Details
2024-01-21 15:00:26 +01:00
kolaente c5ba7fcb73
fix(editor): focus the editor when clicking on the whole edit container
continuous-integration/drone/push Build is passing Details
2024-01-21 13:52:13 +01:00
kolaente 5a25685d53
fix(editor): don't bubble up changes when no changes were made
continuous-integration/drone/push Build is passing Details
Related https://community.vikunja.io/t/saving-an-empty-description-in-kanban-view-break-ui/1914/3
2024-01-21 13:44:40 +01:00
kolaente da311fce9e
fix(kanban): ensure text and icon color only depends on the card background, not on the color scheme
continuous-integration/drone/push Build is passing Details
Related https://github.com/go-vikunja/frontend/issues/135#issuecomment-1900701258
2024-01-21 00:10:05 +01:00
kolaente 0fdf1ca027
fix(notifications): read indicator size
continuous-integration/drone/push Build is passing Details
2024-01-21 00:01:04 +01:00
kolaente f8e907a8c1
fix(notifications): always left-align notification text
continuous-integration/drone/push Build is passing Details
2024-01-20 23:59:57 +01:00
kolaente af7ca8ad8f
fix(project): always use the appropriate color for task estimate during deletion dialoge
continuous-integration/drone/push Build is passing Details
2024-01-20 23:54:03 +01:00
nor 92f7d9ded5 feat: datepicker locale support (#3878)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3878
Reviewed-by: konrad <k@knt.li>
Co-authored-by: nor <zorodey@outlook.com>
Co-committed-by: nor <zorodey@outlook.com>
2024-01-20 18:50:00 +00:00
renovate 41ccaea78b fix(deps): update dependency date-fns to v3.3.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-20 06:19:03 +00:00
renovate c5696f3e2a chore(deps): update dependency vite to v5.0.12
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-20 00:19:28 +00:00
renovate 898707664c fix(deps): update sentry-javascript monorepo to v7.94.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-19 13:19:38 +00:00
renovate d0b5bef68a chore(deps): update dependency happy-dom to v13.2.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build was killed Details
2024-01-19 00:20:24 +00:00
renovate e395d4efdb fix(deps): update dependency vue to v3.4.15
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-18 14:19:39 +00:00
renovate ce54132868 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-18 07:18:54 +00:00
renovate 07d4d1e537 fix(deps): update dependency floating-vue to v5.2.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 13:19:31 +00:00
renovate a701b0452e fix(deps): update dependency floating-vue to v5.1.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 12:18:43 +00:00
renovate af65efcd27 chore(deps): update dev-dependencies (major) (#3890)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3890
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-17 09:17:35 +00:00
renovate dc2afb9e8d chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 07:18:31 +00:00
WofWca e123d4f825 chore(perf): import some modules dynamically (#3179)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3179
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2024-01-16 14:24:24 +00:00
renovate b72c963256 fix(deps): update dependency vue to v3.4.14
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-16 12:10:13 +00:00
renovate 149bbf17eb fix(deps): update dependency floating-vue to v5.1.0
continuous-integration/drone/push Build is passing Details
2024-01-16 12:01:54 +00:00
renovate 265d60cf42 fix(deps): update vueuse to v10.7.2
continuous-integration/drone/push Build is failing Details
2024-01-16 12:01:44 +00:00
renovate 23c9f51e73 fix(deps): update dependency sortablejs to v1.15.2
continuous-integration/drone/push Build is passing Details
2024-01-16 12:01:11 +00:00
renovate ff697d0c7a chore(deps): update dev-dependencies
continuous-integration/drone/push Build is passing Details
2024-01-16 11:50:52 +00:00
renovate 00588cf59f chore(deps): pin node.js (#3895)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #3895
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-16 11:49:00 +00:00
renovate 01089f4f3d fix(deps): update tiptap to v2.1.16 (#3892)
continuous-integration/drone/push Build was killed Details
Reviewed-on: #3892
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-16 11:17:28 +00:00
kolaente a7461d1ddd
chore(deps): increase renovate timeout
continuous-integration/drone/push Build is passing Details
2024-01-16 12:15:04 +01:00
kolaente a451189bb6
fix(test): make date assertion not brittle
continuous-integration/drone/push Build is passing Details
2024-01-16 10:36:29 +01:00
kolaente bf9af27fc3
fix(task): update due date when marking a task done
continuous-integration/drone/push Build is failing Details
2024-01-15 23:33:02 +01:00
kolaente 5619fda0f2
fix(task): bubble date changes from the picker up
continuous-integration/drone/push Build is failing Details
Resolves https://github.com/go-vikunja/frontend/issues/142
2024-01-15 23:23:57 +01:00
kolaente 167953b26b
fix(editor): use higher-contrast colors for links and code
continuous-integration/drone/push Build is passing Details
2024-01-15 22:11:24 +01:00
kolaente 664bf0a5f4
fix(tasks): make sure tasks show up if their parent task is not available in the current view
continuous-integration/drone/push Build is passing Details
Related https://github.com/go-vikunja/frontend/issues/136
Related https://community.vikunja.io/t/subtasks-are-hidden-when-parent-tasks-are-moved/1911
2024-01-15 21:46:47 +01:00
kolaente 5e991f3024
fix: lint
continuous-integration/drone/push Build is passing Details
2024-01-15 16:21:00 +01:00
kolaente 28050d9cd5
fix(labels): make color reset work
continuous-integration/drone/push Build is failing Details
2024-01-15 14:00:08 +01:00
kolaente e94b71d577
fix(editor): list icons
continuous-integration/drone/push Build is passing Details
2024-01-15 13:39:17 +01:00
renovate 336ce217d3 chore(deps): update node.js to v20.11 (#3888)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3888
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-11 11:30:14 +00:00
Frederick [Bot] ce01085951 chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-01-11 00:11:57 +00:00
kolaente 96a6d43a3f
fix(quick add magic): ensure month is removed from task text
continuous-integration/drone/push Build is passing Details
Resolves #3874
2024-01-10 23:54:42 +01:00
kolaente 13d63e34aa
fix(task): don't immediately re-trigger date change when nothing changed
continuous-integration/drone/push Build is passing Details
Resolves https://community.vikunja.io/t/reminder-duplication/76/21?u=kolaente
2024-01-10 23:27:14 +01:00
renovate a8441c72b8 fix(deps): update dependency vue to v3.4.8 (#3886)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3886
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 20:16:50 +00:00
renovate 230fa6ce66 fix(deps): update dependency floating-vue to v5 (#3887)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3887
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 18:18:53 +00:00
renovate 069c491fbd fix(deps): update sentry-javascript monorepo to v7.93.0 (#3859)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3859
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 16:01:02 +00:00
renovate a9eae95d67 chore(deps): update pnpm to v8.14.1 (#3885)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3885
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 15:57:10 +00:00
renovate 50502d9d11 fix(deps): update vueuse to v10.7.1 (#3872)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3872
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 15:04:27 +00:00
renovate 18af6edc82 fix(deps): update tiptap to v2.1.15 (#3884)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3884
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:49:03 +00:00
renovate d048b61eb3 fix(deps): update dependency floating-vue to v2.0.0 (#3883)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3883
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:36:14 +00:00
renovate 996607e670 fix(deps): update dependency dompurify to v3.0.8 (#3881)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3881
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:35:50 +00:00
renovate e33ebe1831 fix(deps): update dependency vue-i18n to v9.9.0 (#3880)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3880
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 14:27:02 +00:00
renovate 557b0ffec7 chore(deps): update dependency node to v20.11.0
continuous-integration/drone/push Build is passing Details
2024-01-10 12:04:17 +00:00
renovate dae6cdb9d7 fix(deps): update dependency @kyvg/vue3-notification to v3.1.3 (#3864)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3864
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:59:31 +00:00
renovate 158e4d690f chore(deps): update dev-dependencies (#3861)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #3861
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:51:20 +00:00
renovate 691eb84a99 fix(deps): update dependency date-fns to v3 (#3857)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3857
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:29:48 +00:00
renovate 698ee7e163 fix(deps): update dependency axios to v1.6.5 (#3871)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3871
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:28:28 +00:00
renovate ce822573df fix(deps): update dependency vue to v3.4.7 (#3873)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3873
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 11:19:30 +00:00
renovate 198abee01d chore(deps): update pnpm to v8.14.0
continuous-integration/drone/push Build is passing Details
2024-01-10 10:59:39 +00:00
Frederick [Bot] e5bea087be chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-01-09 00:10:45 +00:00
Frederick [Bot] 4956fbb669 chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-01-08 11:50:29 +00:00
kolaente 0351148288
fix(ci): use working crowdin image
continuous-integration/drone/push Build is passing Details
2024-01-07 20:20:14 +01:00
kolaente 654806211e
fix(ci): use working image for crowdin update step
continuous-integration/drone/push Build is passing Details
2024-01-04 13:22:27 +01:00
kolaente 09572dbe61
fix(ci): use working crowdin image
continuous-integration/drone/push Build is passing Details
2023-12-27 15:44:36 +01:00
kolaente fae5b764dd
fix(notifications): unread indicator spacing
continuous-integration/drone/push Build is passing Details
2023-12-23 15:53:17 +01:00
kolaente 7f70471894
feat(reminders): show reminders in notifications bar
continuous-integration/drone/push Build is passing Details
2023-12-23 15:48:29 +01:00
kolaente e98e5a0d2f
fix(openid): use the full path when building the redirect url, not only the host
continuous-integration/drone/push Build is passing Details
Resolves vikunja/api#1661
2023-12-20 13:23:56 +01:00
renovate 21e34d6d54 fix(deps): update dependency @intlify/unplugin-vue-i18n to v2 (#3862)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3862
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-20 11:22:56 +00:00
kolaente 4b71604513
chore: release preparation
continuous-integration/drone/push Build is passing Details
2023-12-19 16:17:19 +01:00
kolaente 609b86e614
chore(deps): update lockfile
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-12-19 15:07:45 +01:00
renovate 4faffd37a7
fix(deps): update dependency @intlify/unplugin-vue-i18n to v1.6.0
(cherry picked from commit 1138285fd9)
2023-12-19 15:07:29 +01:00
renovate 008585d61f
fix(deps): update dependency @github/hotkey to v3
(cherry picked from commit c92f0c8491)
2023-12-19 15:07:29 +01:00
renovate d0ae285663
fix(deps): update dependency @kyvg/vue3-notification to v3.1.2
(cherry picked from commit 15b4c6062c)
2023-12-19 15:07:29 +01:00
renovate 00d0f88798
fix(deps): update dependency vue to v3.3.13
(cherry picked from commit 2c784ecd65)
2023-12-19 15:07:29 +01:00
renovate 4cf31080f8
fix(deps): update sentry-javascript monorepo to v7.88.0
(cherry picked from commit 12caae90ee)
2023-12-19 15:07:25 +01:00
renovate 21e54de3b8 chore(deps): update dev-dependencies (#3856)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3856
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-19 13:48:35 +00:00
renovate 143cabbac5 chore(deps): update pnpm to v8.12.1
continuous-integration/drone/push Build is passing Details
2023-12-19 13:15:53 +00:00
kolaente 0607c97da9
feat(api tokens): allow selecting all permissions
continuous-integration/drone/push Build is passing Details
2023-12-17 18:39:54 +01:00
kolaente be925b29e3
fix(api tokens): make deletion of old tokens work
continuous-integration/drone/push Build is passing Details
2023-12-17 17:47:12 +01:00
kolaente 2541733c71
fix(tasks): prevent endless references
continuous-integration/drone/push Build is passing Details
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
kolaente 4d6fd9ecc4
fix(tasks): favorited sub tasks are not shown in favorites pseudo list
continuous-integration/drone/push Build is passing Details
2023-12-13 19:22:28 +01:00
kolaente 818f31c220
fix(tasks): update sub task relations in list view after they were created
continuous-integration/drone/push Build is passing Details
Resolves #3853
2023-12-13 19:15:48 +01:00
kolaente 34e4862c88
fix(kanban): make sure kanban cards always have text color matching their background
continuous-integration/drone/push Build is passing Details
Resolves https://github.com/go-vikunja/frontend/issues/135
2023-12-13 18:54:48 +01:00
renovate 0d074113f1 chore(deps): update dev-dependencies (#3846)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
kolaente 0730955403
fix(sw): remove debug option via env as it would not be replaced correctly in prod builds
continuous-integration/drone/push Build is passing Details
2023-12-13 18:37:56 +01:00
kolaente 75035ec1f8
fix(navigation): show filter settings dropdown
continuous-integration/drone/push Build is passing Details
Resolves #3851
2023-12-13 18:30:10 +01:00
konrad 923fc4eaa0 fix(editor): keep editor open when emptying content from the outside (#3852)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3852
2023-12-11 23:13:30 +00:00
kolaente ea7dab68ae
fix(editor): add workaround for checklist tiptap bug
continuous-integration/drone/pr Build is passing Details
2023-12-11 23:58:46 +01:00
kolaente 8b9e5e54af
fix(test): use correct file input
continuous-integration/drone/pr Build is failing Details
2023-12-11 23:23:25 +01:00
kolaente a4a2b95dc7
chore: cleanup
continuous-integration/drone/pr Build is failing Details
2023-12-11 22:37:28 +01:00
kolaente 9fdb6a8d24
feat(task): add more tests
continuous-integration/drone/pr Build was killed Details
2023-12-11 22:35:32 +01:00
kolaente fc6b707405
fix(task): use empty description helper everywhere 2023-12-11 22:35:09 +01:00
kolaente 9efe860f26
fix(editor): keep editor open when emptying content from the outside 2023-12-11 21:58:39 +01:00
kolaente af13d68c48
fix(editor): show editor if there is no content initially 2023-12-11 21:55:47 +01:00
kolaente 3cb1e7dede
chore: debug 2023-12-11 21:01:38 +01:00
renovate e27d88785e fix(deps): update dependency vue to v3.3.10 (#3843)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate b1fc5dbd97 chore(deps): update dev-dependencies (major) (#3827)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 1786dee042 fix(deps): update dependency @github/hotkey to v2.3.1 (#3845)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 8491caf419 fix(deps): update vueuse to v10.7.0 (#3844)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3844
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-06 12:16:48 +00:00
renovate 3b15293b47 chore(deps): update dev-dependencies (#3842)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate e65f13eaa3 fix(deps): update dependency sortablejs to v1.15.1 (#3841)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 25561f3229 fix(deps): update dependency vue-i18n to v9.8.0 (#3833)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 97d149c2f5 fix(deps): update sentry-javascript monorepo to v7.85.0 (#3831)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
kolaente c8809d899a
fix(kanban): check if doneBucketId is set
continuous-integration/drone/push Build is passing Details
2023-12-01 15:10:48 +01:00
renovate 203041ae36 fix(deps): update dependency vue to v3.3.9 (#3837)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 11d11012e7 chore(deps): update dependency node (#3834)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 30046c7ff5 chore(deps): update dev-dependencies (#3835)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 668ff753b3 fix(deps): update font awesome to v6.5.1 (#3839)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 0b68ab93e1 fix(deps): update tiptap to v2.1.13 (#3840)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3840
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-12-01 10:50:22 +00:00
renovate af22d2e88a chore(deps): update pnpm to v8.11.0
continuous-integration/drone/push Build is passing Details
2023-12-01 10:31:40 +00:00
kolaente 611e9feb6d
chore(deps): update sub-dependencies
continuous-integration/drone/push Build is passing Details
2023-11-28 22:50:15 +01:00
kolaente e770496524
fix(editor): don't check parent checkbox when child label was clicked
continuous-integration/drone/push Build is passing Details
2023-11-27 13:11:24 +01:00
kolaente 1cbb93ea9b
fix(tasks): make sure tasks are fully clickable
continuous-integration/drone/push Build is passing Details
Resolves #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
renovate 282ec3164b chore(deps): update dev-dependencies (#3829)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
kolaente 4868ac824e
feat(i18n): add Slovene language for selection in the ui
continuous-integration/drone/push Build is passing Details
2023-11-21 22:14:15 +01:00
kolaente 0c58ea1ade
fix(editor): don't crash when the component isn't completely mounted
continuous-integration/drone/push Build is passing Details
2023-11-21 13:25:55 +01:00
kolaente f45303c2e3
fix(editor): image paste handling 2023-11-21 13:23:05 +01:00
kolaente c3e53970de
chore(deps): update lockfile
continuous-integration/drone/push Build is passing Details
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
renovate cfd46dc39b fix(deps): update vueuse to v10.6.1 (#3822)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3822
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-20 20:40:34 +00:00
kolaente debae2326e
fix(editor): don't create empty "blob" files when pasting images
continuous-integration/drone/push Build is passing Details
2023-11-20 12:35:19 +01:00
renovate 23d670525d chore(deps): update dessant/repo-lockdown action to v4
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-11-20 05:13:05 +00:00
kolaente 2967019cd9
feat(editor): mark a checkbox item as done when clicking on its text
continuous-integration/drone/push Build is passing Details
2023-11-18 18:01:09 +01:00
kolaente d3497c96d7
fix(editor): correctly resolve images in descriptions
continuous-integration/drone/push Build is failing Details
Resolves #3808
2023-11-18 17:17:14 +01:00
kolaente bd83294ac0
fix(editor): alignment and focus states
continuous-integration/drone/push Build is failing Details
2023-11-18 17:03:47 +01:00
kolaente 6c4f1e1cbf
fix(editor): make initial editor mode (preview/edit) work
continuous-integration/drone/push Build is failing Details
2023-11-18 16:54:29 +01:00
kolaente fa269f155a
chore(filter): remove debug log
continuous-integration/drone/push Build is failing Details
2023-11-18 16:44:51 +01:00
kolaente 602d15985b
fix(filter): don't immediately re-trigger prepareFilter
continuous-integration/drone/push Build is failing Details
2023-11-18 16:40:20 +01:00
renovate cc3c1a9429 chore(deps): update dev-dependencies (#3828)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3828
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-18 14:21:37 +00:00
renovate cfd49864e1 fix(deps): update dependency axios to v1.6.2 (#3820)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3820
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-18 14:21:02 +00:00
renovate 6711a08de9 fix(deps): update sentry-javascript monorepo to v7.80.1 (#3819)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3819
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 22:50:00 +00:00
renovate 7fe33c6662 fix(deps): update dependency @types/sortablejs to v1.15.5 (#3818)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3818
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 22:49:51 +00:00
renovate e61b215dc1 fix(deps): update dependency ufo to v1.3.2 (#3824)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3824
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 22:03:25 +00:00
renovate 3b5cb1ade3 fix(deps): update dependency vue-i18n to v9.7.0 (#3825)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3825
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 22:01:43 +00:00
renovate 89e28cbdf2 chore(deps): update dev-dependencies (#3826)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3826
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-17 21:59:59 +00:00
renovate e9e836f068 chore(deps): update pnpm to v8.10.5
continuous-integration/drone/push Build is passing Details
2023-11-17 21:40:42 +00:00
kolaente aa5e11915e
fix(filter): don't prevent entering date math strings
continuous-integration/drone/push Build is passing Details
Resolves https://community.vikunja.io/t/filter-setting-s/1791/2
2023-11-17 19:38:55 +01:00
kolaente 7f279c98e1
fix(tasks): don't use the filter for upcoming when one is set for the home page
continuous-integration/drone/push Build is passing Details
Resolves https://github.com/go-vikunja/frontend/issues/132
2023-11-17 19:08:08 +01:00
kolaente 3c1861eb6a
fix(settings): move overdue remindeer time below
continuous-integration/drone/push Build is passing Details
2023-11-17 19:03:58 +01:00
kolaente 75262b716f
fix(kanban): opening a task from the kanban board and then reloading the page should not crash everything when then navigating back
continuous-integration/drone/push Build is passing Details
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
ThatHurleyGuy 7e623d919e fix(filters): infinite loop when creating filters with dates (#3061)
continuous-integration/drone/push Build is passing Details
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: #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
kolaente 3f42ce2b34
fix(filter): make other filters are not available for project selection
continuous-integration/drone/push Build is passing Details
2023-11-15 12:47:19 +01:00
renovate 8b8da40265 chore(deps): update dev-dependencies (#3821)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
kolaente 59a7360608
feat(migration): proper wording for async migration
continuous-integration/drone/push Build is passing Details
2023-11-09 00:14:37 +01:00
renovate 29e128c64c chore(deps): update dev-dependencies (#3813)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3813
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 19:24:57 +00:00
renovate cec50d912c fix(deps): update dependency vue to v3.3.8 (#3814)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3814
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 19:24:39 +00:00
renovate 53564ec46c fix(deps): update dependency @types/lodash.clonedeep to v4.5.9 (#3817)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3817
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 17:07:48 +00:00
renovate e9cd7aac69 fix(deps): update dependency @intlify/unplugin-vue-i18n to v1.5.0 (#3812)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3812
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 17:06:56 +00:00
renovate a47bfb3ff1 fix(deps): update dependency @types/is-touch-device to v1.0.2 (#3816)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3816
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 15:06:23 +00:00
renovate 86eb4da2e3 fix(deps): update dependency @fortawesome/vue-fontawesome to v3.0.5 (#3815)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3815
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 15:03:14 +00:00
renovate d1882e9c3f fix(deps): update dependency vue-i18n to v9.6.5 (#3807)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3807
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-07 14:55:42 +00:00
renovate 974755ffc2 fix(deps): update sentry-javascript monorepo to v7.77.0 (#3805)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
kolaente 218d72494a
fix: lint
continuous-integration/drone/push Build is passing Details
2023-11-03 12:39:02 +01:00
kolaente bde212d432
fix(editor): change description when switching between tasks
continuous-integration/drone/push Build is failing Details
2023-11-03 12:36:20 +01:00
kolaente 64a8dd189b
fix(editor): always set mode to preview after save 2023-11-03 12:27:21 +01:00
kolaente ba766a29af
fix(editor): check for empty content
continuous-integration/drone/push Build is passing Details
2023-11-03 12:22:38 +01:00
renovate e02a106c64 chore(deps): update dev-dependencies (#3811)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3811
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-03 11:06:43 +00:00
renovate ccdc5d4868 fix(deps): update dependency @github/hotkey to v2.3.0 (#3810)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3810
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-11-03 11:04:11 +00:00
renovate 9240739a4b chore(deps): update pnpm to v8.10.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-11-01 17:10:19 +00:00
renovate 963d91c4d5 chore(deps): update dev-dependencies (#3806)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3806
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-31 12:14:10 +00:00
renovate f33d154b37 fix(deps): update dependency @github/hotkey to v2.2.0 (#3809)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
kolaente 8e0ba555ed
fix(editor): check for almost empty editor value
continuous-integration/drone/push Build is passing Details
2023-10-29 19:42:20 +01:00
kolaente 9cf81e1478
fix(editor): use modelValue directly to update values in the editor 2023-10-29 19:39:38 +01:00
renovate 4350d78178 fix(deps): update dependency vue-i18n to v9.6.1 (#3803)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3803
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-27 09:10:07 +00:00
renovate cea27bb754 chore(deps): update dev-dependencies (#3802)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3802
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-27 08:35:43 +00:00
renovate 4b7f8c265d fix(deps): update dependency axios to v1.6.0 (#3801)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3801
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-27 08:35:05 +00:00
renovate 412e6e77b4 chore(deps): update dependency @types/node to v20 (#3796)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3796
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 21:21:54 +00:00
renovate 45abdda680 fix(deps): update dependency vue-i18n to v9.6.0 (#3800)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3800
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 21:21:20 +00:00
renovate 0b2188d72d fix(deps): update sentry-javascript monorepo to v7.75.1 (#3798)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3798
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 21:20:56 +00:00
renovate 143a2a105d fix(deps): update dependency vue to v3.3.7 (#3799)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3799
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 20:51:14 +00:00
renovate 68d18934d8 chore(deps): update dev-dependencies (#3793)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3793
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 11:24:54 +00:00
renovate cea3274a90 chore(deps): update dependency node (#3797)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #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
kolaente 5f2787e18d
fix(task): use editor as preview first, then check for edit
continuous-integration/drone/push Build is passing Details
2023-10-22 22:47:52 +02:00
kolaente 2eac17ed57
fix(editor): commands list in dark mode
continuous-integration/drone/push Build is passing Details
2023-10-22 19:58:25 +02:00
renovate 8d566c9371 fix(deps): update dependency @types/is-touch-device to v1.0.1 (#3786)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3786
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 17:51:25 +00:00
renovate ab5118b51b chore(deps): update pnpm to v8.9.2
continuous-integration/drone/push Build is passing Details
2023-10-22 17:42:39 +00:00
kolaente 0b294de132 fix(task): make sure the modal close button is not overlapped with the title field (#3256)
continuous-integration/drone/push Build is passing Details
Resolves #3252
Reviewed-on: #3256
Co-authored-by: kolaente <k@knt.li>
Co-committed-by: kolaente <k@knt.li>
2023-10-22 17:40:50 +00:00
renovate c1149273f9 fix(deps): update dependency @types/sortablejs to v1.15.4 (#3788)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3788
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 17:31:22 +00:00
renovate 7496be5a44 fix(deps): update tiptap to v2.1.12 (#3790)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3790
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 17:22:46 +00:00
renovate a35b0f64a2 fix(deps): update dependency lowlight to v2.9.0 (#3789)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3789
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 17:01:34 +00:00
renovate 6c2b30f8ef fix(deps): update dependency @types/lodash.clonedeep to v4.5.8 (#3787)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3787
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 15:22:23 +00:00
renovate daa720669a fix(deps): update sentry-javascript monorepo to v7.74.1 (#3778)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3778
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-22 15:12:47 +00:00
konrad 26fc9b4e4f feat: move from easymde to tiptap editor (#2222)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2222
2023-10-22 13:48:57 +00:00
kolaente 37af478811
chore(editor): remove marked usages
continuous-integration/drone/pr Build is passing Details
2023-10-22 15:18:39 +02:00
kolaente 22223a56bd
chore(editor): remove converting markdown
continuous-integration/drone/pr Build is failing Details
2023-10-22 15:12:36 +02:00
kolaente c367b70ccc
chore(editor): remove unused components 2023-10-22 15:12:23 +02:00
kolaente 9103ad8505
chore(deps): remove unused dependencies
continuous-integration/drone/pr Build is passing Details
2023-10-22 14:46:21 +02:00
kolaente e4eaca82e1
fix(editor): add missing dependency
continuous-integration/drone/pr Build is passing Details
2023-10-22 14:29:48 +02:00
kolaente 229beec1d1
fix(editor): lint
continuous-integration/drone/pr Build is failing Details
2023-10-22 14:24:10 +02:00
kolaente 803f9c81c2
fix(editor): make tests work with changed structure
continuous-integration/drone/pr Build is failing Details
2023-10-22 14:21:28 +02:00
kolaente c6b123734b
feat(editor): add tests to check rendering of task description 2023-10-22 14:21:13 +02:00
kolaente 0154b2a475
fix(editor): allow checking a checkbox even when the editor is set to read only
continuous-integration/drone/pr Build is failing Details
2023-10-22 13:26:01 +02:00
kolaente 32ca8853bc
fix(editor): don't use global shortcut when anything is focused 2023-10-22 12:48:31 +02:00
kolaente f6d5cbcf6f
feat(editor): only load attachment images when rendering is done
continuous-integration/drone/pr Build is failing Details
2023-10-22 12:38:34 +02:00
kolaente d7503dc4a2
feat(editor): edit mode
continuous-integration/drone/pr Build is failing Details
2023-10-22 12:08:27 +02:00
kolaente 632e3c5a0b
fix(editor): duplicate name for extension
continuous-integration/drone/pr Build is failing Details
2023-10-22 11:02:03 +02:00
kolaente c61f1a45fb
fix(editor): placeholder showing or not showing
continuous-integration/drone/pr Build is failing Details
2023-10-22 11:00:42 +02:00
kolaente 2f3196ef86
fix(editor): duplicate name
continuous-integration/drone/pr Build is failing Details
2023-10-22 10:50:16 +02:00
kolaente 2864854cd4
fix(editor): don't prevent typing editor focus shortcut when other instance of an editor is focused already
continuous-integration/drone/pr Build is failing Details
2023-10-22 10:47:13 +02:00
kolaente a453449fea
fix(editor): reset on empty
continuous-integration/drone/pr Build is failing Details
2023-10-22 10:41:34 +02:00
kolaente abb6630b4b
feat(editor): add comment when pressing ctrl enter 2023-10-22 10:40:12 +02:00
kolaente 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
kolaente 19a78f1f75
fix(editor): lint
continuous-integration/drone/pr Build is failing Details
2023-10-21 19:52:56 +02:00
kolaente d6a41fa518
chore(editor): remove old editor component
continuous-integration/drone/pr Build is failing Details
2023-10-21 19:48:17 +02:00
kolaente 859fc1e94e
feat(editor): edit shortcut to set focus into the editor
continuous-integration/drone/pr Build is failing Details
2023-10-21 19:46:46 +02:00
kolaente aa715dd9e1
chore(editor): cleanup unused options 2023-10-21 19:46:25 +02:00
kolaente daa2ed3b1c
feat(editor): allow passing placeholder down
continuous-integration/drone/pr Build is failing Details
2023-10-21 19:33:32 +02:00
kolaente 1443e23f18
chore(editor): cleanup 2023-10-21 19:29:27 +02:00
kolaente 34420b623c
fix(editor): use edit enable 2023-10-21 19:29:19 +02:00
kolaente 80dc35eabb
fix(editor): actions styling 2023-10-21 19:29:00 +02:00
kolaente cb1d2b3834
fix(editor): always show placeholder when empty
continuous-integration/drone/pr Build is failing Details
2023-10-21 19:18:28 +02:00
kolaente 0ef775e9b9
feat(editor): improve overall styling
continuous-integration/drone/pr Build is failing Details
2023-10-21 19:08:11 +02:00
kolaente a7e4e3adf9
feat(editor): add placeholder 2023-10-21 19:02:55 +02:00
kolaente d005875bbf
chore(editor): make sure all tiptap dependencies are updated as one 2023-10-21 18:57:17 +02:00
kolaente dc3ee112bd
chore(editor): cleanup
continuous-integration/drone/pr Build is failing Details
2023-10-21 18:47:04 +02:00
kolaente 9b20dc1899
feat(editor): properly bubble changes when they are made
continuous-integration/drone/pr Build is failing Details
2023-10-21 18:40:30 +02:00
kolaente 22103626b8
fix(editor): make checklist indicator work again
continuous-integration/drone/pr Build is failing Details
2023-10-21 18:18:17 +02:00
kolaente 4f2d7b3ce2
feat(editor): add uploading an image on save
continuous-integration/drone/pr Build is failing Details
2023-10-21 18:03:59 +02:00
kolaente 76d31c84ad
feat(editor): add tooltips for everything
continuous-integration/drone/pr Build is failing Details
2023-10-21 17:48:35 +02:00
kolaente 66c37f10e0
chore(editor): cleanup
continuous-integration/drone/pr Build is failing Details
2023-10-21 14:10:26 +02:00
kolaente 0b2aa723a6
feat(editor): open links when clicking on them
continuous-integration/drone/pr Build is failing Details
2023-10-21 14:07:39 +02:00
kolaente d75a963d08
feat(editor): add code highlighting
continuous-integration/drone/pr Build is failing Details
2023-10-21 14:06:47 +02:00
kolaente beefc1d5ef
feat(editor): add bubble menu
continuous-integration/drone/pr Build is failing Details
2023-10-21 14:02:53 +02:00
kolaente 17c23d9463
feat(editor): make image upload work via slash command
continuous-integration/drone/pr Build is failing Details
2023-10-21 13:28:59 +02:00
kolaente 02ab1b8c0a
feat(editor): add all slash commands
continuous-integration/drone/pr Build is failing Details
2023-10-21 13:00:12 +02:00
kolaente e81c98fe5b
chore(editor): format 2023-10-21 11:52:20 +02:00
kolaente 3bf806f00c
fix(editor): add missing dependencies for commands
continuous-integration/drone/pr Build is failing Details
2023-10-21 11:46:02 +02:00
kolaente aea3f86a8f
feat(editor): add command list example
continuous-integration/drone/pr Build is failing Details
copied from 252acb32d2/demos/src/Experiments/Commands/Vue
2023-10-21 11:33:49 +02:00
kolaente 5297208d92
feat(editor): move all editor related components into one folder
continuous-integration/drone/pr Build is failing Details
2023-10-21 11:15:17 +02:00
kolaente c84bcfddba
feat(editor): add proper description for all buttons
continuous-integration/drone/pr Build is failing Details
2023-10-21 11:10:43 +02:00
kolaente 0772acbead
chore(editor): format 2023-10-21 10:53:41 +02:00
renovate 123c665d9d chore(deps): update dependency eslint to v8.52.0 (#3785)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3785
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-21 08:08:26 +00:00
kolaente 4f3efe4454
feat(editor): resolve and load attachment images from content
continuous-integration/drone/pr Build is failing Details
2023-10-20 23:03:38 +02:00
kolaente 671c658868
fix(editor): actually populate loaded data into the editor
continuous-integration/drone/pr Build is failing Details
2023-10-20 22:52:21 +02:00
kolaente 05bf7ccf0b
feat(editor): image upload
continuous-integration/drone/pr Build is failing Details
2023-10-20 22:43:10 +02:00
renovate b76acb15c7 chore(deps): update dev-dependencies (major) (#3741)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3741
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 19:34:11 +00:00
kolaente 953361c480
chore(deps): update lockfile
continuous-integration/drone/pr Build is failing Details
2023-10-20 17:10:55 +02:00
kolaente 8b60e5b2c8
fix(editor): add icons for clearing marks and nodes 2023-10-20 17:10:06 +02:00
kolaente faf93a6088
feat(editor): enable table 2023-10-20 17:10:06 +02:00
kolaente 8e07d9647a
chore(editor): move checklist to the other lists 2023-10-20 17:10:06 +02:00
kolaente 08959fdb77
fix(editor): focus state 2023-10-20 17:10:06 +02:00
kolaente e716fd1bf9
fix(editor): list styling 2023-10-20 17:10:06 +02:00
kolaente 63865028b8
feat(editor): make task list work 2023-10-20 17:10:06 +02:00
kolaente e760ce45e4
fix(editor): checklist button icon 2023-10-20 17:10:06 +02:00
kolaente af9eb358ee
fix(editor): permission check for table editing 2023-10-20 17:10:06 +02:00
kolaente ddcf6bf0a5
fix(editor): image button icon 2023-10-20 17:10:06 +02:00
kolaente 9c71e30efe
chore(editor): add break icon 2023-10-20 17:10:06 +02:00
kolaente c58ad47782
chore(editor): use typed props definition 2023-10-20 17:10:05 +02:00
kolaente ca0d9e6bd5
chore(editor): add horizontal line icon 2023-10-20 17:10:05 +02:00
kolaente ad3234b19f
chore(deps): update dependencies 2023-10-20 17:10:05 +02:00
Dominik Pschenitschni 24b8915983
wip: tiptap editor 2023-10-20 17:10:05 +02:00
kolaente 01c2acdf34
chore(deps): update flake
continuous-integration/drone/push Build is passing Details
2023-10-20 17:09:55 +02:00
kolaente ff2b4b8bf4
feat(notifications): add option to mark all as read
continuous-integration/drone/push Build is passing Details
2023-10-20 16:52:03 +02:00
kolaente d73c62a424
fix(task): correctly build task identifier 2023-10-20 16:52:03 +02:00
renovate cac41a1c86 fix(deps): update dependency vue to v3.3.6 (#3784)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #3784
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 14:01:41 +00:00
kolaente aeed4b3a3b
fix(settings): allow removing the default project via settings
continuous-integration/drone/push Build is passing Details
2023-10-20 16:01:22 +02:00
renovate 8992caadf9 fix(deps): update dependency marked to v9.1.2 (#3774)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3774
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 13:17:59 +00:00
renovate b2b423aee5 chore(deps): update dependency node to v18.18.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-10-20 12:08:04 +00:00
konrad 5d991e539b feat: webhooks (#3783)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3783
2023-10-20 11:56:59 +00:00
renovate accde483cb fix(deps): update dependency vue to v3.3.5 (#3782)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3782
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 11:38:11 +00:00
renovate 2d5e560b74 chore(deps): update dev-dependencies (#3780)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3780
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-20 11:37:50 +00:00
kolaente 5d91134b48
fix(tasks): use mousedown event instead of click to close the task popup
continuous-integration/drone/push Build is passing Details
Resolves #3779
2023-10-20 13:14:46 +02:00
kolaente 0e5415a2c9
fix(webhooks): styling
continuous-integration/drone/pr Build is passing Details
2023-10-20 12:32:55 +02:00
kolaente 779aad1b2d
feat(webhooks): add form validation 2023-10-20 12:32:46 +02:00
kolaente 3d2fe4cf65
feat(webhooks): add webhook management form 2023-10-18 20:12:48 +02:00
renovate c38421466b chore(deps): update dev-dependencies (#3776)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
kolaente b9717f504d
feat(i18n): add arabic to list of selectable languages
continuous-integration/drone/push Build is passing Details
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
renovate eb591fdd3c chore(deps): update dev-dependencies (#3769)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3769
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-13 13:59:06 +00:00
renovate 23e1899fce fix(deps): update dependency @github/hotkey to v2.1.1 (#3770)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3770
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-13 13:32:18 +00:00
renovate 22968ba639 fix(deps): update dependency pinia to v2.1.7 (#3771)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3771
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-13 13:17:47 +00:00
renovate b345f0ad61 fix(deps): update sentry-javascript monorepo to v7.74.0 (#3772)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #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
renovate a5f7487bd0 fix(deps): update dependency marked to v9.1.1 (#3768)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3768
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 21:42:37 +00:00
kolaente ae001c6ca7
fix(user): allow openid users to request their deletion
continuous-integration/drone/push Build is passing Details
Resolves https://community.vikunja.io/t/delete-user-not-possible-when-using-oidc/1689/4
2023-10-11 19:07:11 +02:00
kolaente f0b340a9c7
feat(task): save currently opened task with control/meta + s
continuous-integration/drone/push Build is passing Details
2023-10-11 17:44:17 +02:00
renovate 40538df392 fix(deps): update dependency @github/hotkey to v2.1.0 (#3766)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3766
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 09:01:48 +00:00
renovate fc17b16c60 chore(deps): update dependency sass to v1.69.2 (#3767)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3767
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 07:34:29 +00:00
renovate 6c59b4e2d2 fix(deps): update dependency highlight.js to v11.9.0 (#3763)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3763
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-10 19:21:25 +00:00
renovate e8a38ed482 fix(deps): update vueuse to v10.5.0 (#3762)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3762
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-10 18:28:10 +00:00
renovate f5604dcac6 chore(deps): update dependency node to v18.18.1
continuous-integration/drone/push Build is passing Details
2023-10-10 16:24:22 +00:00
renovate 049c644959 chore(deps): update dev-dependencies (#3761)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3761
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-10 16:23:29 +00:00
kolaente 07b1e9a6b7
chore: add pr lockdown
continuous-integration/drone/push Build is passing Details
2023-10-10 18:02:40 +02:00
renovate 7aedf6ee1f chore(deps): update pnpm to v8.9.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-10-09 12:09:54 +00:00
renovate bc9bfe3300 fix(deps): update dependency marked to v9.1.0 (#3760)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3760
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-05 07:06:20 +00:00
renovate c2005c6c71 chore(deps): update dev-dependencies (#3757)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate d7cbade64e chore(deps): update node.js to v20.8 (#3756)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3756
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-03 14:59:12 +00:00
renovate 06b00b77ed fix(deps): update dependency dompurify to v3.0.6 (#3754)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3754
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-03 14:58:26 +00:00
renovate f2392cef7e fix(deps): update dependency vue-router to v4.2.5 (#3755)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3755
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-03 14:19:27 +00:00
renovate e6f2b36d88 fix(deps): update dependency dayjs to v1.11.10 (#3753)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3753
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-03 14:16:01 +00:00
kolaente 7d2fcd26f2 chore(deps): update lockfile
continuous-integration/drone/push Build is passing Details
2023-10-03 13:34:11 +00:00
renovate 369e22f224 fix(deps): update dependency ufo to v1.3.1 2023-10-03 13:34:11 +00:00
kolaente 0ff5b90ebd chore(deps): update lockfile
continuous-integration/drone/push Build is passing Details
2023-10-03 12:16:07 +00:00
kolaente e89245e42d fix(deps): update dependency @infectoone/vue-ganttastic to v2.2.0 2023-10-03 12:16:07 +00:00
kolaente 35717a1e29 fix(deps): update dependency @kyvg/vue3-notification to v3 2023-10-03 12:16:07 +00:00
kolaente e46cf2fa1b fix(deps): update dependency vue-i18n to v9.5.0 2023-10-03 12:16:07 +00:00
kolaente 1ad6d5a66b fix(deps): update dependency @intlify/unplugin-vue-i18n to v1 2023-10-03 12:16:07 +00:00
kolaente 4754bb99f0 fix(deps): update dependency marked to v9 2023-10-03 12:16:07 +00:00
kolaente 608e99fffc chore(deps): update pnpm to v8.8.0 2023-10-03 12:16:07 +00:00
kolaente d6741d19e3 fix(deps): update sentry-javascript monorepo to v7.73.0 2023-10-03 12:16:07 +00:00
kolaente ec52be0353 fix(deps): update dependency axios to v1.5.1 2023-10-03 12:16:07 +00:00
renovate 2d5ab4f5f0 chore(deps): update dependency node to v18.18.0
continuous-integration/drone/push Build is passing Details
2023-10-03 11:45:51 +00:00
Giacomo Rossetto a71755e408 feat(gantt): implement dynamic sizing on small date ranges (#3750)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
kolaente 66c7a05cdb
fix(project): correctly show project color next to project title in list view
continuous-integration/drone/push Build is passing Details
Resolves https://community.vikunja.io/t/color-bubbles-not-showing-after-import/1648
2023-09-29 20:46:02 +02:00
kolaente 287daf9125
fix(auth): silently discard invalid auth tokens and log the user out
continuous-integration/drone/push Build is passing Details
2023-09-29 10:38:00 +02:00
kolaente 8507808058
fix(tasks): ignore empty lines when adding multiple tasks at once
continuous-integration/drone/push Build is passing Details
Resolves #3732 (comment)
2023-09-29 10:06:38 +02:00
kolaente 93c155dd2f
fix(quick actions): always open quick actions with hotkey, even if other inputs are focused
continuous-integration/drone/push Build is passing Details
Resolves #3743
2023-09-29 09:48:06 +02:00
kolaente b1c4748969
fix(background): unsplash author credit in dark mode
continuous-integration/drone/push Build is passing Details
2023-09-29 09:33:30 +02:00
kolaente 0887860b2a
fix(task): priority label spacing
continuous-integration/drone/push Build is passing Details
2023-09-29 09:32:00 +02:00
kolaente 0b1c8ed4dd
fix(attachments): layout and coloring in dark mode
continuous-integration/drone/push Build is passing Details
2023-09-29 09:28:51 +02:00
kolaente 3988a3f9f8
fix(task): correct spacing to task and project title
continuous-integration/drone/push Build is passing Details
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
renovate 5c23343172 chore(deps): update dev-dependencies (#3747)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3747
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-27 07:34:49 +00:00
renovate 01a4335c7c chore(deps): update node.js to v20.7 (#3736)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #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
renovate 0235b14997 chore(deps): update dev-dependencies (#3746)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3746
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-15 11:59:29 +00:00
renovate 8eafa23269 chore(deps): update dev-dependencies (#3740)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3740
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-09-13 12:35:39 +00:00
kolaente 5c95a721f4
fix(filter): don't show other filters in project selection in saved filter
continuous-integration/drone/push Build is passing Details
Resolves https://github.com/go-vikunja/frontend/issues/125
2023-09-13 11:41:47 +02:00
kolaente a6eb804fae
fix(gantt): open task with double click from the gantt chart
continuous-integration/drone/push Build is passing Details
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
kolaente b126a7f7ff
fix(navigation): don't hide color bubble in navigation on touch devices
continuous-integration/drone/push Build is failing Details
Related discussion: https://community.vikunja.io/t/board-color-sticker/1607
2023-09-07 16:36:11 +02:00
kolaente f256fc3843
fix(list view): align nested subtasks with the parent text
continuous-integration/drone/push Build is failing Details
2023-09-07 13:47:52 +02:00
kolaente e41712647d
feat(list view): show subtasks nested
continuous-integration/drone/push Build is failing Details
Resolves #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
kolaente 2d61a349ac
feat(task): immediately set focus on the task search input when opening the related tasks menu
continuous-integration/drone/push Build is failing Details
2023-09-06 18:50:38 +02:00
kolaente 54c527c23f
feat(tasks): make the whole task in list view clickable
continuous-integration/drone/push Build is failing Details
Resolves #3172
Closes #3176
Closes #3180
2023-09-06 18:31:30 +02:00
kolaente 4d8c6622d2
fix(ci): use correct secret key to push
continuous-integration/drone/push Build is failing Details
2023-09-06 18:03:09 +02:00
kolaente 3f3d4b1682
feat(labels): assign random color when creating labels
continuous-integration/drone/push Build is failing Details
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
kolaente 9c46d064ac
feat(quick add magic): allow using the project identifier via quick add magic
continuous-integration/drone/push Build is failing Details
Related discussion: https://community.vikunja.io/t/using-shorter-list-names-in-quick-add-magic/895
2023-09-06 16:51:23 +02:00
kolaente 0d3143d465
fix(quick add magic): headline 2023-09-06 16:45:12 +02:00
kolaente 337c3e5e3e
fix: lint
continuous-integration/drone/push Build is failing Details
2023-09-06 16:31:07 +02:00
kolaente 3bb5308141
feat(task): group related task action buttons
continuous-integration/drone/push Build is failing Details
2023-09-06 16:30:00 +02:00
kolaente 3fec92283b
fix(task): priority label sizing and positioning in different environments
continuous-integration/drone/push Build is failing Details
2023-09-06 15:58:52 +02:00
kolaente beb016400e
feat(task): move task priority to the front when showing tasks inline
continuous-integration/drone/push Build is failing Details
2023-09-06 15:53:40 +02:00
kolaente 7746d39161
fix(task): remove wrong repeat types
continuous-integration/drone/push Build is failing Details
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 #3585 (comment)
2023-09-06 15:41:48 +02:00
kolaente b187e8c1b6
fix(ci): pin used node version to 20.5 to avoid build issues
continuous-integration/drone/push Build is failing Details
Related https://github.com/vitejs/vite/issues/14299
2023-09-06 15:33:04 +02:00
kolaente 0ecda46af9
chore(deps): update dependencies
continuous-integration/drone/push Build is failing Details
2023-09-06 15:30:00 +02:00
kolaente 59dc927b5c
feat(i18n): update translations only once a day
continuous-integration/drone/push Build is failing Details
2023-09-06 15:24:44 +02:00
kolaente a13953ee14
fix(i18n): add upload files config
continuous-integration/drone/push Build is failing Details
2023-09-06 15:22:51 +02:00
kolaente a4b836d395
feat(i18n): run translation update directly
continuous-integration/drone/push Build was killed Details
2023-09-06 15:19:32 +02:00
kolaente 16b46b0f4d
feat(i18n): update crowdin sync to use v2 api
continuous-integration/drone/push Build is failing Details
2023-09-06 15:18:27 +02:00
kolaente 184110b986
fix(gantt): update the gantt view when switching between projects
continuous-integration/drone/push Build is failing Details
Resolves https://community.vikunja.io/t/listing-subprojects-tasks/1567/5
2023-09-06 13:25:27 +02:00
kolaente 1918947c0b
fix(tasks): reset page number when applying filters
continuous-integration/drone/push Build is failing Details
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
kolaente 4e5823183e
fix(tasks): update api route
continuous-integration/drone/push Build is failing Details
2023-09-06 10:41:39 +02:00
kolaente 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
kolaente c4adcf4655
chore: include version json string in release zip
continuous-integration/drone/push Build is passing Details
2023-09-04 22:19:37 +02:00
kolaente b1fe3fe29b
fix: don't render route modal when no properties are defined
continuous-integration/drone/push Build is passing Details
2023-09-04 21:33:50 +02:00
kolaente 5720a86bc3
fix(task): don't reload the kanban board when opening a task
continuous-integration/drone/push Build is passing Details
2023-09-04 21:01:42 +02:00
kolaente 86eff7d49e
fix(task): don't reload the kanban board when opening a task
continuous-integration/drone/push Build is failing Details
2023-09-04 20:27:55 +02:00
kolaente 7a9aa7771b
fix(tasks): play pop sound directly and not from store
continuous-integration/drone/push Build is passing Details
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 #3292
2023-09-04 20:14:43 +02:00
kolaente abbc11528e
feat(tasks): update due date text every minute
continuous-integration/drone/push Build is passing Details
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
kolaente 725fd1ad46
feat: improve error message for invalid API url
continuous-integration/drone/push Build is passing Details
Resolves #3680
2023-09-04 13:37:17 +02:00
kolaente 44754fac0f
chore(ci): sign drone config
continuous-integration/drone/push Build is passing Details
2023-09-04 13:11:59 +02:00
kolaente 7f2d92138e
fix: lint
continuous-integration/drone/push Build is pending Details
2023-09-04 13:11:31 +02:00
kolaente 95be0d1d32
fix(build): don't download Puppeteer when building for prod
continuous-integration/drone/push Build is pending Details
2023-09-04 13:07:48 +02:00
kolaente f63c39a578
feat(assignees): improve avatar list consistency
continuous-integration/drone/push Build is failing Details
Resolves #3354
2023-09-04 13:03:39 +02:00
kolaente 270e32290a
fix(quick add magic): ignore common task indention when adding multiple tasks at once
continuous-integration/drone/push Build is failing Details
Resolves #3732
2023-09-04 11:24:10 +02:00
kolaente 9cf8696b84
fix(docker): set correct default value for custom logo url
continuous-integration/drone/push Build is failing Details
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
konrad 04ba1011cc feat: add setting for default bucket
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3735
2023-09-03 15:14:44 +00:00
kolaente 52c0efe0ce
feat(kanban): add icon for bucket collapse
continuous-integration/drone/pr Build is passing Details
2023-09-03 16:32:29 +02:00
kolaente c803020537
feat(kanban): add setting for default bucket 2023-09-03 16:32:29 +02:00
kolaente 3373b5fc45
feat(kanban): save done bucket with project instead of bucket 2023-09-03 16:32:29 +02:00
kolaente f6d1db3595
fix: tests
continuous-integration/drone/push Build is passing Details
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
kolaente ed8fb71ff0
feat: add demo mode warning message
continuous-integration/drone/push Build is passing Details
Resolves #2453
2023-09-01 18:09:19 +02:00
konrad 28f2551d87 feat: api tokens
continuous-integration/drone/push Build is failing Details
Reviewed-on: #3733
2023-09-01 14:34:56 +00:00
kolaente cec480ad80
fix(api tokens): lint
continuous-integration/drone/pr Build is passing Details
2023-09-01 15:59:16 +02:00
kolaente 830a3745ba
feat(api tokens): show warning if token has expired
continuous-integration/drone/pr Build was killed Details
2023-09-01 13:32:00 +02:00
kolaente 49104c65b6
fix(api tokens): expiry of tokens in a number of days 2023-09-01 13:28:32 +02:00
kolaente 984978fe6d
feat(api tokens): format permissions and groups human-readable 2023-09-01 13:25:37 +02:00
kolaente bd7b973559
feat(api tokens): add deleting api tokens 2023-09-01 13:18:00 +02:00
kolaente 0bb85870db
feat(api tokens): allow custom selection of expiry dates 2023-09-01 13:07:20 +02:00
kolaente 021f92303d
feat(api tokens): validate title field when creating a new token 2023-09-01 12:56:23 +02:00
kolaente e47ad021a3
feat(api tokens): add token creation form 2023-09-01 12:47:32 +02:00
kolaente 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
renovate 49261a6fcc chore(deps): update dev-dependencies (#3726)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3726
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-08-29 11:59:08 +00:00
kolaente 5630c90dee
fix(task): show related tasks form with shortcut even when there are already other related tasks
continuous-integration/drone/push Build is passing Details
Resolves https://github.com/go-vikunja/frontend/issues/122
2023-08-29 13:57:12 +02:00
konrad 47d589002c feat: quick actions improvments
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3728
2023-08-29 11:24:00 +00:00
kolaente 99e2161c09
fix: lint
continuous-integration/drone/pr Build is passing Details
2023-08-29 12:46:30 +02:00
kolaente 20f61baf03
fix(quick actions): search for tasks within a project when specifying a project with quick add magic
continuous-integration/drone/pr Build is failing Details
2023-08-29 12:45:05 +02:00
kolaente 4e6b99544e
fix(quick actions): don't show projects when searching for labels or tasks
continuous-integration/drone/pr Build is failing Details
2023-08-29 12:38:59 +02:00
kolaente d57e1909c4
feat(quick actions): show labels as labels and tasks with all of their details 2023-08-29 12:33:43 +02:00
kolaente 99d8fbdfa7
feat(quick actions): show tasks for a label when selecting it
continuous-integration/drone/pr Build is failing Details
2023-08-29 11:11:37 +02:00
kolaente 442d0342a9
fix(quick actions): project search
continuous-integration/drone/pr Build is passing Details
2023-08-29 10:08:47 +02:00
kolaente a4b369470a
fix(quick actions): invalid class prop 2023-08-29 09:57:13 +02:00
kolaente 0ca73e0851
fix(quick actions): always search for projects 2023-08-29 09:41:53 +02:00
kolaente 9fc829115f
fix(quick actions): project filter 2023-08-29 09:34:08 +02:00
kolaente 1e19548563
chore(quick actions): format 2023-08-29 09:33:56 +02:00
kolaente c327d86a71
feat(quick actions): show task identifier 2023-08-29 09:33:41 +02:00
kolaente 3044560759
feat(quick actions): show done tasks last 2023-08-29 09:21:11 +02:00
kolaente c3f85fcb19
chore: format 2023-08-29 09:19:52 +02:00
renovate 53434952d3 chore(deps): update dev-dependencies (#3721)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3721
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-08-27 15:27:00 +00:00
renovate e9b0640660 fix(deps): update dependency @vueuse/core to v10.4.0 (#3723)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3723
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-08-27 15:00:38 +00:00
renovate ae57e5d314
chore(deps): update pnpm to v8.7.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-08-27 16:33:22 +02:00
kolaente 6e7928b2e4
fix(i18n): hungarian translation
continuous-integration/drone/push Build is passing Details
2023-08-27 16:32:06 +02:00
kolaente 47639b00f8
feat(i18n): add hungarian translation for selection
continuous-integration/drone/push Build is failing Details
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
kolaente f7e22c8c56
fix(auth): correctly redirect the user to the last visited page after login
continuous-integration/drone/push Build is passing Details
Resolves #3682
2023-08-24 12:15:45 +02:00
kolaente a9fb306e46
fix(i18n): fall back to browser language if the configured user language is invalid
continuous-integration/drone/push Build is passing Details
2023-08-24 11:37:23 +02:00
kolaente 58a1f46668
fix(projects): don't suggest to create a new task in an empty filter
continuous-integration/drone/push Build is passing Details
2023-08-24 11:32:28 +02:00
kolaente 6cbbe17bd8
fix(filters): don't allow marking a filter as favorite
continuous-integration/drone/push Build is passing Details
2023-08-24 11:30:57 +02:00
kolaente c01957aae2
fix: lint
continuous-integration/drone/push Build is passing Details
2023-08-24 11:27:31 +02:00
kolaente 1ad03877fb
fix(menu): separate favorite and saved filter projects from other projects
continuous-integration/drone/push Build is failing Details
Resolves #3710
Resolves https://github.com/go-vikunja/frontend/issues/119
2023-08-24 11:27:20 +02:00
kolaente fc72a82a2a
fix(task): duplicate attribute
continuous-integration/drone/push Build is failing Details
2023-08-24 11:18:17 +02:00
kolaente 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)
continuous-integration/drone/push Build is failing Details
2023-08-23 21:56:08 +02:00
davidangel cade3df3e9 feat: allow custom logo via environment variable (#3685)
continuous-integration/drone/push Build is passing Details
Related discussion: https://community.vikunja.io/t/change-vikunja-logo-and-color-scheme/621

Reviewed-on: #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
kolaente 37975c1931 chore(deps): update lockfile
continuous-integration/drone/push Build is passing Details
2023-08-23 06:37:45 +00:00
renovate 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
renovate dbed4caca7 fix(deps): update dependency ufo to v1.2.0
continuous-integration/drone/push Build is failing Details
2023-08-21 11:06:04 +00:00
renovate 6d79c9b2ed chore(deps): update pnpm to v8.6.12
continuous-integration/drone/push Build is failing Details
2023-08-21 11:03:59 +00:00
renovate 24f0822a12 fix(deps): update dependency @vueuse/core to v10.3.0
continuous-integration/drone/push Build is failing Details
2023-08-21 11:03:34 +00:00
renovate f3ba778fd3 fix(deps): update dependency pinia to v2.1.6
continuous-integration/drone/push Build is running Details
2023-08-21 11:03:08 +00:00
renovate 55a7255728 fix(deps): update font awesome to v6.4.2
continuous-integration/drone/push Build is passing Details
2023-08-21 10:58:33 +00:00
renovate 2b47e5faec chore(deps): update node.js to v18.17.1
continuous-integration/drone/push Build is passing Details
2023-08-21 10:58:19 +00:00
kolaente 9f82ec4162
chore: update lockfile
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build encountered an error Details
2023-08-21 12:22:58 +02:00
kolaente 64c90c7fe8
chore: update flake 2023-08-21 12:22:30 +02:00
renovate 9fe3d2b2bc chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is failing Details
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
renovate 7e1cfebf6a chore(deps): update dependency @types/node to v18.17.0
continuous-integration/drone/push Build is passing Details
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
renovate d466d50712 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-22 01:04:58 +00:00
renovate cf945f2841 fix(deps): update sentry-javascript monorepo to v7.60.0
continuous-integration/drone/push Build is failing Details
2023-07-21 14:06:11 +00:00
renovate 74df69fc94 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
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
renovate 026db7acad chore(deps): update node.js to v18.17.0
continuous-integration/drone/push Build is passing Details
2023-07-20 07:24:20 +00:00
renovate 61e97bfe1c fix(deps): update sentry-javascript monorepo to v7.59.3
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-19 17:04:49 +00:00
renovate 6530d26b82 chore(deps): update pnpm to v8.6.9
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-18 11:04:21 +00:00
renovate 1e24fe8bab fix(deps): update sentry-javascript monorepo to v7.59.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-18 09:04:49 +00:00
renovate f786c2b8a2 chore(deps): update dev-dependencies
continuous-integration/drone/push Build is passing Details
2023-07-18 08:50:37 +00:00
renovate e596e2c3bc fix(deps): update sentry-javascript monorepo to v7.59.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
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
renovate 668b910190 chore(deps): update pnpm to v8.6.8
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-17 10:04:41 +00:00
renovate 2bdc532f89 fix(deps): update dependency codemirror to v5.65.14
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-17 08:04:36 +00:00
renovate 933c7d8acc chore(deps): update dev-dependencies
continuous-integration/drone/push Build is passing Details
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
renovate d19a5d9714 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-15 00:04:50 +00:00
renovate 90cad1c8dd chore(deps): update dev-dependencies
continuous-integration/drone/push Build is passing Details
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
kolaente d7ce8dd320
fix(quick add magic): repeating intervals in words
continuous-integration/drone/push Build is passing Details
Resolves #3676
2023-07-13 18:20:30 +02:00
kolaente 25b110ce48
fix(quick add magic): annually and variants spelling
continuous-integration/drone/push Build is passing Details
Related to #3676
2023-07-13 18:05:19 +02:00
renovate 33fe5e4f20 fix(deps): update sentry-javascript monorepo to v7.58.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-13 12:04:38 +00:00
renovate 129ef769a3 fix(deps): update sentry-javascript monorepo to v7.58.0
continuous-integration/drone/push Build is passing Details
2023-07-13 06:47:22 +00:00
renovate 9030a9f7c1 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
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
renovate 890e7e1f52 chore(deps): update dependency vite to v4.4.3
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-12 00:05:46 +00:00
renovate 9e0f2b0249 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-11 11:04:54 +00:00
renovate 9a34c522b2 fix(deps): update dependency dompurify to v3.0.5
continuous-integration/drone/push Build is passing Details
2023-07-11 10:24:42 +00:00
renovate 60dd698fad chore(deps): update dev-dependencies to v6
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-11 10:05:43 +00:00
kolaente 15ecafdf04
fix: don't try to load buckets for project id 0
continuous-integration/drone/push Build is passing Details
2023-07-11 10:42:20 +02:00
kolaente 8902c15f7e
fix: correctly resolve kanban board in the background when moving a task
continuous-integration/drone/push Build is failing Details
Resolves F-951
2023-07-10 18:10:14 +02:00
kolaente d5358793de
chore: provide better error messages when refreshing user info fails
continuous-integration/drone/push Build is passing Details
2023-07-10 12:20:40 +02:00
renovate 33798b8d88 chore(deps): update pnpm to v8.6.7
continuous-integration/drone/push Build is passing Details
2023-07-10 06:43:02 +00:00
renovate c686e8677b chore(deps): update dependency caniuse-lite to v1.0.30001514
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-10 00:05:28 +00:00
renovate 5acc1696a9 fix(deps): update dependency @intlify/unplugin-vue-i18n to v0.12.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-08 19:04:46 +00:00
renovate c4976b6a22 chore(deps): update dependency vite to v4.4.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-08 00:06:23 +00:00
renovate d88ff594e1 fix(deps): update dependency marked to v5.1.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-07-07 15:04:29 +00:00
271 changed files with 21895 additions and 8501 deletions

View File

@ -42,7 +42,7 @@ steps:
# - .cache
- name: dependencies
image: node:20-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -55,7 +55,7 @@ steps:
# - restore-cache
- name: lint
image: node:20-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -66,7 +66,7 @@ steps:
- dependencies
- name: build-prod
image: node:20-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -77,7 +77,7 @@ steps:
- dependencies
- name: test-unit
image: node:20-alpine
image: node:20.11.0-alpine
pull: always
commands:
- corepack enable && pnpm config set store-dir .cache/pnpm
@ -87,7 +87,7 @@ steps:
- name: typecheck
failure: ignore
image: node:20-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -202,7 +202,7 @@ steps:
# - .cache
- name: build
image: node:20-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -210,6 +210,7 @@ steps:
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
@ -225,6 +226,7 @@ steps:
image: kolaente/zip
pull: always
commands:
- cp src/version.json dist
- cd dist
- zip -r ../vikunja-frontend-unstable.zip *
- cd ..
@ -283,7 +285,7 @@ steps:
# - .cache
- name: build
image: node:20-alpine
image: node:20.11.0-alpine
pull: always
environment:
PNPM_CACHE_FOLDER: .cache/pnpm
@ -306,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 ..
@ -472,24 +475,25 @@ name: update-translations
trigger:
branch:
- main
include:
- main
event:
- cron
include:
- cron
cron:
- update_translations
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
@ -509,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:
from_secret: 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: 6a566550cac03e9f3f9bbccab95fda4b342233bd63a1409cb5f634b1c744c326
hmac: a044c7c4db3c2a11299d4d118397e9d25be36db241723a1bbd0a2f9cc90ffdac
...

View File

@ -6,7 +6,6 @@
# (2) Comment in and adjust the values as needed.
# VITE_IS_ONLINE=true
# VITE_WORKBOX_DEBUG=false
# SENTRY_AUTH_TOKEN=YOUR_TOKEN
# SENTRY_ORG=vikunja
# SENTRY_PROJECT=frontend-oss

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',
},
}

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.'

2
.nvmrc
View File

@ -1 +1 @@
18.16.1
20.11.0

View File

@ -9,6 +9,500 @@ All releases can be found on https://code.vikunja.io/frontend/releases.
The releases aim at the api versions which is why there are missing versions.
## [0.22.1] - 2024-01-28
### Bug Fixes
* *(auth)* Correctly construct redirect url from current window href
* *(ci)* Use working crowdin image
* *(ci)* Use working image for crowdin update step
* *(ci)* Use working crowdin image
* *(color picker)* When picking a color, the color picker should not be black afterwards
* *(editor)* List icons
* *(editor)* Use higher-contrast colors for links and code
* *(editor)* Don't bubble up changes when no changes were made
* *(editor)* Focus the editor when clicking on the whole edit container
* *(editor)* Render images without crashing
* *(editor)* Use a stable image id to prevent constant re-rendering
* *(editor)* Use manual input prompt instead of window.prompt
* *(filter)* Validate filter title field after loading a filter for edit
* *(kanban)* Ensure text and icon color only depends on the card background, not on the color scheme
* *(kanban)* Make sure the checklist summary uses the correct text color
* *(kanban)* Make sure spacing between assignees and other task details works out evenly
* *(labels)* Make color reset work
* *(labels)* Text and background combination in dark mode
* *(notifications)* Unread indicator spacing
* *(notifications)* Always left-align notification text
* *(notifications)* Read indicator size
* *(openid)* Use the full path when building the redirect url, not only the host
* *(openid)* Use the calculated redirect url when authenticating with openid providers
* *(project)* Always use the appropriate color for task estimate during deletion dialoge
* *(quick add magic)* Ensure month is removed from task text
* *(table view)* Make sure popup does not overlap
* *(task)* Don't immediately re-trigger date change when nothing changed
* *(task)* Bubble date changes from the picker up
* *(task)* Update due date when marking a task done
* *(task)* Don't show edit button when the user does not have permission to edit the task
* *(task)* Don't show assignee edit buttons and input when the user does not have the permission to edit
* *(tasks)* Make sure tasks show up if their parent task is not available in the current view
* *(tasks)* Don't load tasks multiple times when viewing list or gantt view
* *(test)* Make date assertion not brittle
* Lint ([5e991f3](5e991f3024f7856420614171ec66468eb2e2df63))
### Dependencies
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v2 (#3862)
* *(deps)* Update pnpm to v8.14.0
* *(deps)* Update dependency vue to v3.4.7 (#3873)
* *(deps)* Update dependency axios to v1.6.5 (#3871)
* *(deps)* Update dependency date-fns to v3 (#3857)
* *(deps)* Update dev-dependencies (#3861)
* *(deps)* Update dependency @kyvg/vue3-notification to v3.1.3 (#3864)
* *(deps)* Update dependency node to v20.11.0
* *(deps)* Update dependency vue-i18n to v9.9.0 (#3880)
* *(deps)* Update dependency dompurify to v3.0.8 (#3881)
* *(deps)* Update dependency floating-vue to v2.0.0 (#3883)
* *(deps)* Update tiptap to v2.1.15 (#3884)
* *(deps)* Update vueuse to v10.7.1 (#3872)
* *(deps)* Update pnpm to v8.14.1 (#3885)
* *(deps)* Update sentry-javascript monorepo to v7.93.0 (#3859)
* *(deps)* Update dependency floating-vue to v5 (#3887)
* *(deps)* Update dependency vue to v3.4.8 (#3886)
* *(deps)* Update node.js to v20.11 (#3888)
* *(deps)* Increase renovate timeout
* *(deps)* Update tiptap to v2.1.16 (#3892)
* *(deps)* Pin node.js (#3895)
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency sortablejs to v1.15.2
* *(deps)* Update vueuse to v10.7.2
* *(deps)* Update dependency floating-vue to v5.1.0
* *(deps)* Update dependency vue to v3.4.14
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies (major) (#3890)
* *(deps)* Update dependency floating-vue to v5.1.1
* *(deps)* Update dependency floating-vue to v5.2.0
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency vue to v3.4.15
* *(deps)* Update dependency happy-dom to v13.2.0
* *(deps)* Update sentry-javascript monorepo to v7.94.1
* *(deps)* Update dependency vite to v5.0.12
* *(deps)* Update dependency date-fns to v3.3.0
* *(deps)* Update dev-dependencies
* *(deps)* Update pnpm to v8.14.2
* *(deps)* Update dependency date-fns to v3.3.1
* *(deps)* Update dev-dependencies to v6.19.1
* *(deps)* Update pnpm to v8.14.3
* *(deps)* Update sentry-javascript monorepo to v7.95.0
* *(deps)* Update dev-dependencies
* *(deps)* Update dependency axios to v1.6.6
* *(deps)* Update dev-dependencies
* *(deps)* Update sentry-javascript monorepo to v7.97.0
* *(deps)* Update sentry-javascript monorepo to v7.98.0
* *(deps)* Update dependency axios to v1.6.7
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
### Features
* *(reminders)* Show reminders in notifications bar
* Datepicker locale support (#3878) ([92f7d9d](92f7d9ded5d56b95ba7d647eba01372f6ef682ad))
### Miscellaneous Tasks
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(i18n)* Update translations via Crowdin
* *(perf)* Import some modules dynamically (#3179)
* Only show webhooks overview table when there are webhooks ([326bfb5](326bfb557ab359fa154b163f5dd957928f46d3ec))
* Only show webhooks overview table when there are webhooks ([631b02d](631b02d2eedc4a403b7c55f1c56ceaeca5379bf5))
## [0.22.0] - 2023-12-19
### Bug Fixes
* *(api tokens)* Expiry of tokens in a number of days
* *(api tokens)* Lint
* *(api tokens)* Make deletion of old tokens work
* *(api tokens)* Show a token after it was created
* *(attachments)* Layout and coloring in dark mode
* *(auth)* Correctly redirect the user to the last visited page after login
* *(auth)* Silently discard invalid auth tokens and log the user out
* *(background)* Unsplash author credit in dark mode
* *(build)* Don't download Puppeteer when building for prod
* *(ci)* Pin used node version to 20.5 to avoid build issues
* *(ci)* Use correct secret key to push
* *(docker)* Set correct default value for custom logo url
* *(editor)* Actions styling
* *(editor)* Actually populate loaded data into the editor
* *(editor)* Add icons for clearing marks and nodes
* *(editor)* Add missing dependencies for commands
* *(editor)* Add missing dependency
* *(editor)* Add workaround for checklist tiptap bug
* *(editor)* Alignment and focus states
* *(editor)* Allow checking a checkbox even when the editor is set to read only
* *(editor)* Always set mode to preview after save
* *(editor)* Always show placeholder when empty
* *(editor)* Change description when switching between tasks
* *(editor)* Check for almost empty editor value
* *(editor)* Check for empty content
* *(editor)* Checklist button icon
* *(editor)* Commands list in dark mode
* *(editor)* Correctly resolve images in descriptions
* *(editor)* Don't check parent checkbox when child label was clicked
* *(editor)* Don't crash when the component isn't completely mounted
* *(editor)* Don't create empty "blob" files when pasting images
* *(editor)* Don't prevent typing editor focus shortcut when other instance of an editor is focused already
* *(editor)* Don't use global shortcut when anything is focused
* *(editor)* Duplicate name
* *(editor)* Duplicate name for extension
* *(editor)* Focus state
* *(editor)* Image button icon
* *(editor)* Image paste handling
* *(editor)* Keep editor open when emptying content from the outside
* *(editor)* Keep editor open when emptying content from the outside (#3852)
* *(editor)* Lint
* *(editor)* Lint
* *(editor)* List styling
* *(editor)* Make checklist indicator work again
* *(editor)* Make initial editor mode (preview/edit) work
* *(editor)* Make tests work with changed structure
* *(editor)* Permission check for table editing
* *(editor)* Placeholder showing or not showing
* *(editor)* Reset on empty
* *(editor)* Show editor if there is no content initially
* *(editor)* Use edit enable
* *(editor)* Use modelValue directly to update values in the editor
* *(filter)* Don't immediately re-trigger prepareFilter
* *(filter)* Don't prevent entering date math strings
* *(filter)* Don't show other filters in project selection in saved filter
* *(filter)* Make other filters are not available for project selection
* *(filters)* Don't allow marking a filter as favorite
* *(filters)* Incorrect translation string
* *(filters)* Infinite loop when creating filters with dates (#3061)
* *(gantt)* Open task with double click from the gantt chart
* *(gantt)* Update the gantt view when switching between projects
* *(i18n)* Add upload files config
* *(i18n)* Fall back to browser language if the configured user language is invalid
* *(i18n)* Hungarian translation
* *(kanban)* Check if doneBucketId is set
* *(kanban)* Make sure kanban cards always have text color matching their background
* *(kanban)* Opening a task from the kanban board and then reloading the page should not crash everything when then navigating back
* *(list view)* Align nested subtasks with the parent text
* *(menu)* Separate favorite and saved filter projects from other projects
* *(navigation)* Don't hide color bubble in navigation on touch devices
* *(navigation)* Show filter settings dropdown
* *(project)* Correctly show project color next to project title in list view
* *(projects)* Don't suggest to create a new task in an empty filter
* *(quick actions)* Always open quick actions with hotkey, even if other inputs are focused
* *(quick actions)* Always search for projects
* *(quick actions)* Don't show projects when searching for labels or tasks
* *(quick actions)* Invalid class prop
* *(quick actions)* Project filter
* *(quick actions)* Project search
* *(quick actions)* Search for tasks within a project when specifying a project with quick add magic
* *(quick add magic)* Annually and variants spelling
* *(quick add magic)* Headline
* *(quick add magic)* Ignore common task indention when adding multiple tasks at once
* *(quick add magic)* Repeating intervals in words
* *(settings)* Allow removing the default project via settings
* *(settings)* Move overdue remindeer time below
* *(sw)* Remove debug option via env as it would not be replaced correctly in prod builds
* *(task)* Correct spacing to task and project title
* *(task)* Correctly build task identifier
* *(task)* Don't reload the kanban board when opening a task
* *(task)* Don't reload the kanban board when opening a task
* *(task)* Duplicate attribute
* *(task)* Make sure the modal close button is not overlapped with the title field (#3256)
* *(task)* Priority label sizing and positioning in different environments
* *(task)* Priority label spacing
* *(task)* Remove wrong repeat types
* *(task)* Show related tasks form with shortcut even when there are already other related tasks
* *(task)* Use editor as preview first, then check for edit
* *(task)* Use empty description helper everywhere
* *(tasks)* Don't use the filter for upcoming when one is set for the home page
* *(tasks)* Favorited sub tasks are not shown in favorites pseudo list
* *(tasks)* Ignore empty lines when adding multiple tasks at once
* *(tasks)* Make sure tasks are fully clickable
* *(tasks)* Play pop sound directly and not from store
* *(tasks)* Prevent endless references
* *(tasks)* Reset page number when applying filters
* *(tasks)* Update api route
* *(tasks)* Update sub task relations in list view after they were created
* *(tasks)* Use mousedown event instead of click to close the task popup
* *(test)* Use correct file input
* *(user)* Allow openid users to request their deletion
* *(webhooks)* Styling* Correctly resolve kanban board in the background when moving a task ([8902c15](8902c15f7e9590da075e860f3d35939169ee246a))
* Don't render route modal when no properties are defined ([b1fe3fe](b1fe3fe29b3f7c8e3f1fa279b74f674bc63db232))
* Don't try to load buckets for project id 0 ([15ecafd](15ecafdf04391139da27f38dac9ed915d6220a9a))
* Lint ([218d724](218d72494a088b612e720ca2e9b566c0d3446579))
* Lint ([337c3e5](337c3e5e3e06a9e4928bebffda2e2f223fef398b))
* Lint ([7f2d921](7f2d92138e302188d6000632b4bc9bf053194dee))
* Lint ([99e2161](99e2161c09b1e2b08f3a907bd2e3ad2c71da87d3))
* Lint ([c01957a](c01957aae24696812c80b18c77137b5030fc757a))
* Tests ([f6d1db3](f6d1db35957c4c2fda7a58539a0a39db1b683ccb))
### Dependencies
* *(deps)* Remove unused dependencies
* *(deps)* Update dependencies
* *(deps)* Update dependencies
* *(deps)* Update dependency @fortawesome/vue-fontawesome to v3.0.5 (#3815)
* *(deps)* Update dependency @github/hotkey to v2.1.0 (#3766)
* *(deps)* Update dependency @github/hotkey to v2.1.1 (#3770)
* *(deps)* Update dependency @github/hotkey to v2.2.0 (#3809)
* *(deps)* Update dependency @github/hotkey to v2.3.0 (#3810)
* *(deps)* Update dependency @github/hotkey to v2.3.1 (#3845)
* *(deps)* Update dependency @github/hotkey to v3
* *(deps)* Update dependency @infectoone/vue-ganttastic to v2.2.0
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v0.12.2
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v1
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v1.5.0 (#3812)
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v1.6.0
* *(deps)* Update dependency @kyvg/vue3-notification to v3
* *(deps)* Update dependency @kyvg/vue3-notification to v3.1.2
* *(deps)* Update dependency @types/is-touch-device to v1.0.1 (#3786)
* *(deps)* Update dependency @types/is-touch-device to v1.0.2 (#3816)
* *(deps)* Update dependency @types/lodash.clonedeep to v4.5.8 (#3787)
* *(deps)* Update dependency @types/lodash.clonedeep to v4.5.9 (#3817)
* *(deps)* Update dependency @types/node to v18.17.0
* *(deps)* Update dependency @types/node to v20 (#3796)
* *(deps)* Update dependency @types/sortablejs to v1.15.4 (#3788)
* *(deps)* Update dependency @types/sortablejs to v1.15.5 (#3818)
* *(deps)* Update dependency @vueuse/core to v10.3.0
* *(deps)* Update dependency @vueuse/core to v10.4.0 (#3723)
* *(deps)* Update dependency axios to v1.5.1
* *(deps)* Update dependency axios to v1.6.0 (#3801)
* *(deps)* Update dependency axios to v1.6.2 (#3820)
* *(deps)* Update dependency caniuse-lite to v1.0.30001514
* *(deps)* Update dependency codemirror to v5.65.14
* *(deps)* Update dependency dayjs to v1.11.10 (#3753)
* *(deps)* Update dependency dompurify to v3.0.5
* *(deps)* Update dependency dompurify to v3.0.6 (#3754)
* *(deps)* Update dependency eslint to v8.52.0 (#3785)
* *(deps)* Update dependency highlight.js to v11.9.0 (#3763)
* *(deps)* Update dependency lowlight to v2.9.0 (#3789)
* *(deps)* Update dependency marked to v5.1.1
* *(deps)* Update dependency marked to v9
* *(deps)* Update dependency marked to v9.1.0 (#3760)
* *(deps)* Update dependency marked to v9.1.1 (#3768)
* *(deps)* Update dependency marked to v9.1.2 (#3774)
* *(deps)* Update dependency node (#3797)
* *(deps)* Update dependency node (#3834)
* *(deps)* Update dependency node to v18.18.0
* *(deps)* Update dependency node to v18.18.1
* *(deps)* Update dependency node to v18.18.2
* *(deps)* Update dependency pinia to v2.1.6
* *(deps)* Update dependency pinia to v2.1.7 (#3771)
* *(deps)* Update dependency sass to v1.69.2 (#3767)
* *(deps)* Update dependency sortablejs to v1.15.1 (#3841)
* *(deps)* Update dependency ufo to v1.2.0
* *(deps)* Update dependency ufo to v1.3.1
* *(deps)* Update dependency ufo to v1.3.2 (#3824)
* *(deps)* Update dependency vite to v4.4.2
* *(deps)* Update dependency vite to v4.4.3
* *(deps)* Update dependency vue to v3.3.10 (#3843)
* *(deps)* Update dependency vue to v3.3.13
* *(deps)* Update dependency vue to v3.3.5 (#3782)
* *(deps)* Update dependency vue to v3.3.6 (#3784)
* *(deps)* Update dependency vue to v3.3.7 (#3799)
* *(deps)* Update dependency vue to v3.3.8 (#3814)
* *(deps)* Update dependency vue to v3.3.9 (#3837)
* *(deps)* Update dependency vue-i18n to v9.5.0
* *(deps)* Update dependency vue-i18n to v9.6.0 (#3800)
* *(deps)* Update dependency vue-i18n to v9.6.1 (#3803)
* *(deps)* Update dependency vue-i18n to v9.6.5 (#3807)
* *(deps)* Update dependency vue-i18n to v9.7.0 (#3825)
* *(deps)* Update dependency vue-i18n to v9.8.0 (#3833)
* *(deps)* Update dependency vue-router to v4.2.5 (#3755)
* *(deps)* Update dessant/repo-lockdown action to v4
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies
* *(deps)* Update dev-dependencies (#3721)
* *(deps)* Update dev-dependencies (#3726)
* *(deps)* Update dev-dependencies (#3740)
* *(deps)* Update dev-dependencies (#3746)
* *(deps)* Update dev-dependencies (#3747)
* *(deps)* Update dev-dependencies (#3757)
* *(deps)* Update dev-dependencies (#3761)
* *(deps)* Update dev-dependencies (#3769)
* *(deps)* Update dev-dependencies (#3776)
* *(deps)* Update dev-dependencies (#3780)
* *(deps)* Update dev-dependencies (#3793)
* *(deps)* Update dev-dependencies (#3802)
* *(deps)* Update dev-dependencies (#3806)
* *(deps)* Update dev-dependencies (#3811)
* *(deps)* Update dev-dependencies (#3813)
* *(deps)* Update dev-dependencies (#3821)
* *(deps)* Update dev-dependencies (#3826)
* *(deps)* Update dev-dependencies (#3828)
* *(deps)* Update dev-dependencies (#3829)
* *(deps)* Update dev-dependencies (#3835)
* *(deps)* Update dev-dependencies (#3842)
* *(deps)* Update dev-dependencies (#3846)
* *(deps)* Update dev-dependencies (#3856)
* *(deps)* Update dev-dependencies (major) (#3741)
* *(deps)* Update dev-dependencies (major) (#3827)
* *(deps)* Update dev-dependencies to v6
* *(deps)* Update flake
* *(deps)* Update font awesome to v6.4.2
* *(deps)* Update font awesome to v6.5.1 (#3839)
* *(deps)* Update lockfile
* *(deps)* Update lockfile
* *(deps)* Update lockfile
* *(deps)* Update lockfile
* *(deps)* Update lockfile
* *(deps)* Update lockfile
* *(deps)* Update node.js to v18.17.0
* *(deps)* Update node.js to v18.17.1
* *(deps)* Update node.js to v20.7 (#3736)
* *(deps)* Update node.js to v20.8 (#3756)
* *(deps)* Update pnpm to v8.10.2
* *(deps)* Update pnpm to v8.10.5
* *(deps)* Update pnpm to v8.11.0
* *(deps)* Update pnpm to v8.12.1
* *(deps)* Update pnpm to v8.6.12
* *(deps)* Update pnpm to v8.6.7
* *(deps)* Update pnpm to v8.6.8
* *(deps)* Update pnpm to v8.6.9
* *(deps)* Update pnpm to v8.7.0
* *(deps)* Update pnpm to v8.8.0
* *(deps)* Update pnpm to v8.9.0
* *(deps)* Update pnpm to v8.9.2
* *(deps)* Update sentry-javascript monorepo to v7.58.0
* *(deps)* Update sentry-javascript monorepo to v7.58.1
* *(deps)* Update sentry-javascript monorepo to v7.59.1
* *(deps)* Update sentry-javascript monorepo to v7.59.2
* *(deps)* Update sentry-javascript monorepo to v7.59.3
* *(deps)* Update sentry-javascript monorepo to v7.60.0
* *(deps)* Update sentry-javascript monorepo to v7.73.0
* *(deps)* Update sentry-javascript monorepo to v7.74.0 (#3772)
* *(deps)* Update sentry-javascript monorepo to v7.74.1 (#3778)
* *(deps)* Update sentry-javascript monorepo to v7.75.1 (#3798)
* *(deps)* Update sentry-javascript monorepo to v7.77.0 (#3805)
* *(deps)* Update sentry-javascript monorepo to v7.80.1 (#3819)
* *(deps)* Update sentry-javascript monorepo to v7.85.0 (#3831)
* *(deps)* Update sentry-javascript monorepo to v7.88.0
* *(deps)* Update sub-dependencies
* *(deps)* Update tiptap to v2.1.12 (#3790)
* *(deps)* Update tiptap to v2.1.13 (#3840)
* *(deps)* Update vueuse to v10.5.0 (#3762)
* *(deps)* Update vueuse to v10.6.1 (#3822)
* *(deps)* Update vueuse to v10.7.0 (#3844)
### Features
* *(api tokens)* Add basic api token overview
* *(api tokens)* Add deleting api tokens
* *(api tokens)* Add token creation form
* *(api tokens)* Allow custom selection of expiry dates
* *(api tokens)* Allow selecting all permissions
* *(api tokens)* Format permissions and groups human-readable
* *(api tokens)* Show warning if token has expired
* *(api tokens)* Validate title field when creating a new token
* *(assignees)* Improve avatar list consistency
* *(editor)* Add all slash commands
* *(editor)* Add bubble menu
* *(editor)* Add code highlighting
* *(editor)* Add command list example
* *(editor)* Add comment when pressing ctrl enter
* *(editor)* Add placeholder
* *(editor)* Add proper description for all buttons
* *(editor)* Add tests to check rendering of task description
* *(editor)* Add tooltips for everything
* *(editor)* Add uploading an image on save
* *(editor)* Allow passing placeholder down
* *(editor)* Edit mode
* *(editor)* Edit shortcut to set focus into the editor
* *(editor)* Enable table
* *(editor)* Image upload
* *(editor)* Improve overall styling
* *(editor)* Make image upload work via slash command
* *(editor)* Make task list work
* *(editor)* Mark a checkbox item as done when clicking on its text
* *(editor)* Move all editor related components into one folder
* *(editor)* Only load attachment images when rendering is done
* *(editor)* Open links when clicking on them
* *(editor)* Properly bubble changes when they are made
* *(editor)* Resolve and load attachment images from content
* *(editor)* Save when pressing ctrl enter
* *(gantt)* Implement dynamic sizing on small date ranges (#3750)
* *(i18n)* Add Slovene language for selection in the ui
* *(i18n)* Add arabic to list of selectable languages
* *(i18n)* Add hungarian translation for selection
* *(i18n)* Run translation update directly
* *(i18n)* Update crowdin sync to use v2 api
* *(i18n)* Update translations only once a day
* *(kanban)* Add icon for bucket collapse
* *(kanban)* Add setting for default bucket
* *(kanban)* Save done bucket with project instead of bucket
* *(labels)* Assign random color when creating labels
* *(list view)* Show subtasks nested
* *(migration)* Proper wording for async migration
* *(notifications)* Add option to mark all as read
* *(quick actions)* Show done tasks last
* *(quick actions)* Show labels as labels and tasks with all of their details
* *(quick actions)* Show task identifier
* *(quick actions)* Show tasks for a label when selecting it
* *(quick add magic)* Allow using the project identifier via quick add magic
* *(task)* Add more tests
* *(task)* Group related task action buttons
* *(task)* Immediately set focus on the task search input when opening the related tasks menu
* *(task)* Move task priority to the front when showing tasks inline
* *(task)* Save currently opened task with control/meta + s
* *(tasks)* Make the whole task in list view clickable
* *(tasks)* Update due date text every minute
* *(webhooks)* Add form validation* Allow custom logo via environment variable (#3685) ([cade3df](cade3df3e9a7eca8e0aa9d1553dd5597f0f5a8a2))
* *(webhooks)* Add webhook management form
* Add demo mode warning message ([ed8fb71](ed8fb71ff0b05860f320e2a1fe6c3cb29ed2889a))
* Add setting for default bucket ([04ba101](04ba1011cc3042f657ddb40ee727caf455db8b64))
* Api tokens ([28f2551](28f2551d87b99c59055a4909195e435dbd9794b6))
* Improve error message for invalid API url ([725fd1a](725fd1ad467fb988810cb23f12d372af236bd21d))
* Move from easymde to tiptap editor (#2222) ([26fc9b4](26fc9b4e4f8b96616385f4ca0a77a0ff7ee5eee5))
* Quick actions improvments ([47d5890](47d589002ccef5047a25ea3ad8ebe582c3b0bbc6))
* Webhooks (#3783) ([5d991e5](5d991e539bb3a249447847c13c92ee35d356b902))
### Miscellaneous Tasks
* *(ci)* Sign drone config
* *(editor)* Add break icon
* *(editor)* Add horizontal line icon
* *(editor)* Cleanup
* *(editor)* Cleanup unused options
* *(editor)* Format
* *(editor)* Make sure all tiptap dependencies are updated as one
* *(editor)* Move checklist to the other lists
* *(editor)* Remove converting markdown
* *(editor)* Remove marked usages
* *(editor)* Remove old editor component
* *(editor)* Remove unused components
* *(editor)* Use typed props definition
* *(filter)* Remove debug log
* *(quick actions)* Format* Provide better error messages when refreshing user info fails ([d535879](d5358793de7fc53795329382222e5f3bafc7fba1))
* Add pr lockdown ([07b1e9a](07b1e9a6b76eb7d92640e00a1dec4294efd2947b))
* Cleanup ([a4a2b95](a4a2b95dc7eaad5fe313884eec0d22d7ae5f85c1))
* Debug ([3cb1e7d](3cb1e7dede659acd19410e0611346e0f582f2ff3))
* Format ([c3f85fc](c3f85fcb1988603a58104552b35101b13e93b06e))
* Improve checking for API url '/' suffix (#121) ([311b1d7](311b1d7594cfd03be4d998f4aead041a8ca63f8c))
* Include version json string in release zip ([c4adcf4](c4adcf4655550214ae795d941eb51878f34cedeb))
* Update flake ([64c90c7](64c90c7fe8a77ded21778a798f18862fe966bd1a))
* Update lockfile ([9f82ec4](9f82ec4162151ba32f329cb8e335eff6b21cebd4))
### Other
* *(other)* [skip ci] Updated translations via Crowdin
## [0.21.0] - 2023-07-07
### Bug Fixes
@ -5526,4 +6020,4 @@ Co-committed-by: renovate <renovatebot@kolaente.de>
* Fixed trying to verify an email when there was none
* Fixed loading tasks when the user was not authenticated
## [0.1] - 2018-09-20
## [0.1] - 2018-09-20

View File

@ -3,13 +3,14 @@
# │─││ │││ │ │
# ┘─┘┘─┘┘┘─┘┘─┘
FROM --platform=$BUILDPLATFORM node:20-alpine AS builder
FROM --platform=$BUILDPLATFORM node:20.11.0-alpine AS builder
WORKDIR /build
ARG USE_RELEASE=false
ARG RELEASE_VERSION=unstable
ENV PNPM_CACHE_FOLDER .cache/pnpm/
ENV PUPPETEER_SKIP_DOWNLOAD true
COPY package.json ./
COPY pnpm-lock.yaml ./
@ -57,6 +58,7 @@ 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

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.21.0-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.22.1-brightgreen.svg)](https://dl.vikunja.io)
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
This is the web frontend for Vikunja, written in Vue.js.
@ -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
@ -49,4 +53,4 @@ pnpm run build
```shell
pnpm run lint
```
```

View File

View File

@ -1,40 +0,0 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {ProjectFactory} from '../../factories/project'
import {UserProjectFactory} from '../../factories/users_project'
import {BucketFactory} from '../../factories/bucket'
describe('Editor', () => {
createFakeUserAndLogin()
beforeEach(() => {
ProjectFactory.create(1)
BucketFactory.create(1)
TaskFactory.truncate()
UserProjectFactory.truncate()
})
it('Has a preview with checkable checkboxes', () => {
const tasks = TaskFactory.create(1, {
description: `# Test Heading
* Bullet 1
* Bullet 2
* [ ] Checklist
* [x] Checklist checked
`,
bucket_id: 1,
})
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

@ -5,6 +5,19 @@ import {ProjectFactory} from '../../factories/project'
import {TaskFactory} from '../../factories/task'
import {prepareProjects} from './prepareProjects'
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()
prepareProjects()
@ -207,15 +220,7 @@ describe('Project View Kanban', () => {
})
it('Should remove a task from the board when deleting it', () => {
const projects = ProjectFactory.create(1)
const buckets = BucketFactory.create(2, {
project_id: projects[0].id,
})
const tasks = TaskFactory.create(5, {
project_id: 1,
bucket_id: buckets[0].id,
})
const task = tasks[0]
const task = createSingleTaskInBucket(5)
cy.visit('/projects/1/kanban')
cy.get('.kanban .bucket .tasks .task')
@ -238,4 +243,43 @@ describe('Project 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

@ -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')
@ -112,17 +112,57 @@ describe('Task', () => {
.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}`)
@ -143,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}`)
@ -196,13 +236,13 @@ describe('Task', () => {
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')
@ -213,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])')
@ -227,7 +306,7 @@ 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')
@ -236,7 +315,7 @@ describe('Task', () => {
it('Can move a task to another project', () => {
const projects = ProjectFactory.create(2)
BucketFactory.create(2, {
project_id: '{increment}'
project_id: '{increment}',
})
const tasks = TaskFactory.create(1, {
id: 1,
@ -380,7 +459,7 @@ describe('Task', () => {
addLabelToTaskAndVerify(labels[0].title)
})
it('Can add a label to a task and it shows up on the kanban board afterwards', () => {
const tasks = TaskFactory.create(1, {
id: 1,
@ -389,18 +468,18 @@ describe('Task', () => {
})
const labels = LabelFactory.create(1)
LabelTaskFactory.truncate()
cy.visit(`/projects/${projects[0].id}/kanban`)
cy.get('.bucket .task')
.contains(tasks[0].title)
.click()
addLabelToTaskAndVerify(labels[0].title)
cy.get('.modal-content .close')
.click()
cy.get('.bucket .task')
.should('contain.text', labels[0].title)
})
@ -461,7 +540,87 @@ describe('Task', () => {
cy.get('.global-notification')
.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, {
@ -543,7 +702,7 @@ describe('Task', () => {
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, {
@ -566,7 +725,7 @@ describe('Task', () => {
.click()
cy.get('.reminder-options-popup .card-content .reminder-period input')
.first()
.type('10')
.type('{selectall}10')
cy.get('.reminder-options-popup .card-content .reminder-period select')
.first()
.select('days')
@ -579,7 +738,7 @@ describe('Task', () => {
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, {
@ -609,7 +768,7 @@ describe('Task', () => {
cy.get('.global-notification')
.should('contain', 'Success')
})
it('Can set a priority for a task', () => {
const tasks = TaskFactory.create(1, {
id: 1,
@ -647,7 +806,7 @@ describe('Task', () => {
.select('50%')
cy.get('.global-notification')
.should('contain', 'Success')
cy.wait(200)
cy.get('.task-view .columns.details .column')
@ -656,7 +815,7 @@ describe('Task', () => {
.should('be.visible')
.should('have.value', '0.5')
})
it('Can add an attachment to a task', () => {
TaskAttachmentFactory.truncate()
const tasks = TaskFactory.create(1, {
@ -691,35 +850,119 @@ describe('Task', () => {
cy.get('.bucket .task .footer .icon svg.fa-paperclip')
.should('exist')
})
it('Can check items off a checklist', () => {
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,4 +1,5 @@
import {UserFactory} from '../../factories/user'
import {ProjectFactory} from '../../factories/project'
const testAndAssertFailed = fixture => {
cy.intercept(Cypress.env('API_URL') + '/login*').as('login')
@ -13,26 +14,28 @@ const testAndAssertFailed = fixture => {
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', () => {
@ -57,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

@ -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

@ -13,5 +13,6 @@ sed -ri "s:^(\s*window.SENTRY_ENABLED\s*=)\s*.+:\1 ${VIKUNJA_SENTRY_ENABLED}:g"
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'

1
env.d.ts vendored
View File

@ -25,7 +25,6 @@ interface ImportMetaEnv {
readonly SENTRY_ORG?: string
readonly SENTRY_PROJECT?: string
readonly VITE_WORKBOX_DEBUG?: boolean
readonly VITE_IS_ONLINE: boolean
}

View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1685498995,
"narHash": "sha256-rdyjnkq87tJp+T2Bm1OD/9NXKSsh/vLlPeqCc/mm7qs=",
"lastModified": 1701336116,
"narHash": "sha256-kEmpezCR/FpITc6yMbAh4WrOCiT2zg5pSjnKrq51h5Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9cfaa8a1a00830d17487cb60a19bb86f96f09b27",
"rev": "f5c27c6136db4d76c30e533c20517df6864c46ee",
"type": "github"
},
"original": {

View File

@ -32,6 +32,8 @@
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>

View File

@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@8.6.6",
"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,102 +47,136 @@
"story:preview": "histoire preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/vue-fontawesome": "3.0.3",
"@github/hotkey": "2.0.1",
"@infectoone/vue-ganttastic": "2.1.4",
"@intlify/unplugin-vue-i18n": "0.12.1",
"@kyvg/vue3-notification": "2.9.1",
"@sentry/tracing": "7.57.0",
"@sentry/vue": "7.57.0",
"@vueuse/core": "10.2.1",
"axios": "1.4.0",
"@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.13",
"date-fns": "2.30.0",
"dayjs": "1.11.9",
"dompurify": "3.0.4",
"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.31",
"floating-vue": "2.0.0-beta.24",
"highlight.js": "11.8.0",
"floating-vue": "5.2.2",
"is-touch-device": "1.0.1",
"klona": "2.0.6",
"lodash.debounce": "4.0.8",
"marked": "5.1.0",
"pinia": "2.1.4",
"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.1.2",
"vue": "3.3.4",
"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.3",
"vue-i18n": "9.2.2",
"vue-router": "4.2.4",
"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.4",
"@cypress/vite-dev-server": "5.0.5",
"@cypress/vue": "5.0.5",
"@faker-js/faker": "8.0.2",
"@histoire/plugin-screenshot": "0.16.1",
"@histoire/plugin-vue": "0.16.1",
"@rushstack/eslint-patch": "1.3.2",
"@tsconfig/node18": "18.2.0",
"@types/codemirror": "5.60.8",
"@types/dompurify": "3.0.2",
"@types/flexsearch": "0.7.3",
"@types/is-touch-device": "1.0.0",
"@types/lodash.debounce": "4.0.7",
"@types/marked": "5.0.0",
"@types/node": "18.16.19",
"@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",
"@types/sortablejs": "1.15.1",
"@typescript-eslint/eslint-plugin": "5.61.0",
"@typescript-eslint/parser": "5.61.0",
"@vitejs/plugin-legacy": "4.1.0",
"@vitejs/plugin-vue": "4.2.3",
"@vue/eslint-config-typescript": "11.0.3",
"@vue/test-utils": "2.4.0",
"@vue/tsconfig": "0.4.0",
"autoprefixer": "10.4.14",
"browserslist": "4.21.9",
"caniuse-lite": "1.0.30001513",
"css-has-pseudo": "6.0.0",
"csstype": "3.1.2",
"cypress": "12.17.0",
"esbuild": "0.18.11",
"eslint": "8.44.0",
"eslint-plugin-vue": "9.15.1",
"happy-dom": "10.0.3",
"histoire": "0.16.2",
"postcss": "8.4.25",
"@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": "4.0.0",
"postcss-focus-within": "8.0.0",
"postcss-preset-env": "9.0.0",
"rollup": "3.26.2",
"rollup-plugin-visualizer": "5.9.2",
"sass": "1.63.6",
"start-server-and-test": "2.0.0",
"typescript": "5.1.6",
"vite": "4.4.1",
"vite-plugin-inject-preload": "1.3.1",
"vite-plugin-pwa": "0.16.4",
"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": "4.0.0",
"vitest": "0.33.0",
"vue-tsc": "1.8.4",
"wait-on": "7.0.1",
"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": {

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,11 @@
"extends": [
"config:js-app"
],
"hostRules": [
{
"timeout": 600000
}
],
"packageRules": [
{
"matchPackageNames": ["happy-dom"],
@ -27,6 +32,13 @@
"histoire"
]
},
{
"groupName": "tiptap",
"matchPackagePrefixes": [
"@tiptap/",
"tiptap"
]
},
{
"matchDepTypes": ["devDependencies"],
"groupName": "dev-dependencies",

View File

@ -1,22 +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>
<ContentLinkShare v-else-if="authLinkShare" />
<NoAuthWrapper v-else>
<router-view />
</NoAuthWrapper>
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
<KeyboardShortcuts v-if="keyboardShortcutsActive" />
<Teleport to="body">
<AddToHomeScreen/>
<UpdateNotification/>
<Notification/>
<AddToHomeScreen />
<UpdateNotification />
<Notification />
<DemoMode />
</Teleport>
</ready>
</Ready>
</template>
<script lang="ts" setup>
@ -36,8 +37,6 @@ import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import Ready from '@/components/misc/ready.vue'
import {setLanguage} from '@/i18n'
import AccountDeleteService from '@/services/accountDelete'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
import {useBaseStore} from '@/stores/base'
@ -45,6 +44,10 @@ import {useBaseStore} from '@/stores/base'
import {useColorScheme} from '@/composables/useColorScheme'
import {useBodyClass} from '@/composables/useBodyClass'
import AddToHomeScreen from '@/components/home/AddToHomeScreen.vue'
import DemoMode from '@/components/home/DemoMode.vue'
const importAccountDeleteService = () => import('@/services/accountDelete')
const importMessage = () => import('@/message')
const baseStore = useBaseStore()
const authStore = useAuthStore()
@ -66,8 +69,11 @@ watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
return
}
const messageP = importMessage()
const AccountDeleteService = (await importAccountDeleteService()).default
const accountDeletionService = new AccountDeleteService()
await accountDeletionService.confirm(accountDeletionConfirm)
const {success} = await messageP
success({message: t('user.deletion.confirmSuccess')})
authStore.refreshUserInfo()
}, { immediate: true })

View File

@ -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>

View File

@ -1,17 +1,26 @@
<template>
<div class="base-checkbox" v-cy="'checkbox'">
<div
v-cy="'checkbox'"
class="base-checkbox"
>
<input
type="checkbox"
:id="checkboxId"
type="checkbox"
class="is-sr-only"
:checked="modelValue"
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
:disabled="disabled || undefined"
/>
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
>
<slot name="label" :checkboxId="checkboxId">
<label :for="checkboxId" class="base-checkbox__label">
<slot/>
<slot
name="label"
:checkbox-id="checkboxId"
>
<label
:for="checkboxId"
class="base-checkbox__label"
>
<slot />
</label>
</slot>
</div>

View File

@ -1,27 +1,30 @@
<template>
<transition
name="expandable-slide"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
<div
v-if="initialHeight"
class="expandable-initial-height"
:style="{ maxHeight: `${initialHeight}px` }"
:class="{ 'expandable-initial-height--expanded': open }"
>
<slot />
</div>
<div v-else-if="open" class="expandable">
<slot />
</div>
</transition>
<transition
name="expandable-slide"
@beforeEnter="beforeEnter"
@enter="enter"
@afterEnter="afterEnter"
@enterCancelled="enterCancelled"
@beforeLeave="beforeLeave"
@leave="leave"
@afterLeave="afterLeave"
@leaveCancelled="leaveCancelled"
>
<div
v-if="initialHeight"
class="expandable-initial-height"
:style="{ maxHeight: `${initialHeight}px` }"
:class="{ 'expandable-initial-height--expanded': open }"
>
<slot />
</div>
<div
v-else-if="open"
class="expandable"
>
<slot />
</div>
</transition>
</template>
<script setup lang="ts">

View File

@ -3,9 +3,9 @@ import datemathHelp from './datemathHelp.vue'
</script>
<template>
<Story>
<Variant title="Default">
<datemathHelp />
</Variant>
</Story>
<Story>
<Variant title="Default">
<datemathHelp />
</Variant>
</Story>
</template>

View File

@ -7,21 +7,29 @@
{{ $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>
@ -35,76 +43,79 @@
<h3>{{ $t('input.datemathHelp.supportedUnits') }}</h3>
<table class="table">
<tbody>
<tr>
<td><code>s</code></td>
<td>{{ $t('input.datemathHelp.units.seconds') }}</td>
</tr>
<tr>
<td><code>m</code></td>
<td>{{ $t('input.datemathHelp.units.minutes') }}</td>
</tr>
<tr>
<td><code>h</code></td>
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
</tr>
<tr>
<td><code>H</code></td>
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
</tr>
<tr>
<td><code>d</code></td>
<td>{{ $t('input.datemathHelp.units.days') }}</td>
</tr>
<tr>
<td><code>w</code></td>
<td>{{ $t('input.datemathHelp.units.weeks') }}</td>
</tr>
<tr>
<td><code>M</code></td>
<td>{{ $t('input.datemathHelp.units.months') }}</td>
</tr>
<tr>
<td><code>y</code></td>
<td>{{ $t('input.datemathHelp.units.years') }}</td>
</tr>
<tr>
<td><code>s</code></td>
<td>{{ $t('input.datemathHelp.units.seconds') }}</td>
</tr>
<tr>
<td><code>m</code></td>
<td>{{ $t('input.datemathHelp.units.minutes') }}</td>
</tr>
<tr>
<td><code>h</code></td>
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
</tr>
<tr>
<td><code>H</code></td>
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
</tr>
<tr>
<td><code>d</code></td>
<td>{{ $t('input.datemathHelp.units.days') }}</td>
</tr>
<tr>
<td><code>w</code></td>
<td>{{ $t('input.datemathHelp.units.weeks') }}</td>
</tr>
<tr>
<td><code>M</code></td>
<td>{{ $t('input.datemathHelp.units.months') }}</td>
</tr>
<tr>
<td><code>y</code></td>
<td>{{ $t('input.datemathHelp.units.years') }}</td>
</tr>
</tbody>
</table>
<h3>{{ $t('input.datemathHelp.someExamples') }}</h3>
<table class="table">
<tbody>
<tr>
<td><code>now</code></td>
<td>{{ $t('input.datemathHelp.examples.now') }}</td>
</tr>
<tr>
<td><code>now+24h</code></td>
<td>{{ $t('input.datemathHelp.examples.in24h') }}</td>
</tr>
<tr>
<td><code>now/d</code></td>
<td>{{ $t('input.datemathHelp.examples.today') }}</td>
</tr>
<tr>
<td><code>now/w</code></td>
<td>{{ $t('input.datemathHelp.examples.beginningOfThisWeek') }}</td>
</tr>
<tr>
<td><code>now/w+1w</code></td>
<td>{{ $t('input.datemathHelp.examples.endOfThisWeek') }}</td>
</tr>
<tr>
<td><code>now+30d</code></td>
<td>{{ $t('input.datemathHelp.examples.in30Days') }}</td>
</tr>
<tr>
<td><code>{{ exampleDate }}||+1M/d</code></td>
<td>
<i18n-t keypath="input.datemathHelp.examples.datePlusMonth" scope="global">
<strong>{{ exampleDate }}</strong>
</i18n-t>
</td>
</tr>
<tr>
<td><code>now</code></td>
<td>{{ $t('input.datemathHelp.examples.now') }}</td>
</tr>
<tr>
<td><code>now+24h</code></td>
<td>{{ $t('input.datemathHelp.examples.in24h') }}</td>
</tr>
<tr>
<td><code>now/d</code></td>
<td>{{ $t('input.datemathHelp.examples.today') }}</td>
</tr>
<tr>
<td><code>now/w</code></td>
<td>{{ $t('input.datemathHelp.examples.beginningOfThisWeek') }}</td>
</tr>
<tr>
<td><code>now/w+1w</code></td>
<td>{{ $t('input.datemathHelp.examples.endOfThisWeek') }}</td>
</tr>
<tr>
<td><code>now+30d</code></td>
<td>{{ $t('input.datemathHelp.examples.in30Days') }}</td>
</tr>
<tr>
<td><code>{{ exampleDate }}||+1M/d</code></td>
<td>
<i18n-t
keypath="input.datemathHelp.examples.datePlusMonth"
scope="global"
>
<strong>{{ exampleDate }}</strong>
</i18n-t>
</td>
</tr>
</tbody>
</table>
</card>

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

@ -4,12 +4,18 @@
class="add-to-home-screen"
:class="{'has-update-available': hasUpdateAvailable}"
>
<icon icon="arrow-up-from-bracket" class="add-icon"/>
<icon
icon="arrow-up-from-bracket"
class="add-icon"
/>
<p>
{{ $t('home.addToHomeScreen') }}
</p>
<BaseButton @click="() => hideMessage = true" class="hide-button">
<icon icon="x"/>
<BaseButton
class="hide-button"
@click="() => hideMessage = true"
>
<icon icon="x" />
</BaseButton>
</div>
</template>

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

@ -9,15 +9,30 @@ import {MILLISECONDS_A_HOUR} from '@/constants/date'
const now = useNow({
interval: MILLISECONDS_A_HOUR,
})
const Logo = computed(() => window.ALLOW_ICON_CHANGES && now.value.getMonth() === 5 ? LogoFullPride : LogoFull)
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,7 +1,11 @@
<template>
<BaseButton class="menu-bottom-link" :href="poweredByUrl" target="_blank">
{{ $t('misc.poweredBy') }}
</BaseButton>
<BaseButton
class="menu-bottom-link"
:href="poweredByUrl"
target="_blank"
>
{{ $t('misc.poweredBy') }}
</BaseButton>
</template>
<script setup lang="ts">

View File

@ -2,10 +2,8 @@
<draggable
v-model="availableProjects"
animation="100"
ghostClass="ghost"
ghost-class="ghost"
group="projects"
@start="() => drag = true"
@end="saveProjectPosition"
handle=".handle"
tag="menu"
item-key="id"
@ -19,6 +17,8 @@
{ 'dragging-disabled': !canEditOrder }
],
}"
@start="() => drag = true"
@end="saveProjectPosition"
>
<template #item="{element: project}">
<ProjectsNavigationItem

View File

@ -6,10 +6,13 @@
<div>
<BaseButton
v-if="canCollapse && childProjects?.length > 0"
@click="childProjectsOpen = !childProjectsOpen"
class="collapse-project-button"
@click="childProjectsOpen = !childProjectsOpen"
>
<icon icon="chevron-down" :class="{ 'project-is-collapsed': !childProjectsOpen }"/>
<icon
icon="chevron-down"
:class="{ 'project-is-collapsed': !childProjectsOpen }"
/>
</BaseButton>
<BaseButton
:to="{ name: 'project.index', params: { projectId: project.id} }"
@ -19,21 +22,27 @@
<span
v-if="!canCollapse || childProjects?.length === 0"
class="collapse-project-button-placeholder"
></span>
<div class="color-bubble-handle-wrapper" :class="{'is-draggable': project.id > 0}">
/>
<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
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 lines-handle"
class="icon menu-item-icon handle"
:class="{'has-color-bubble': project.hexColor !== ''}"
>
<icon icon="grip-lines"/>
<icon icon="grip-lines" />
</span>
</div>
<span class="project-menu-title">{{ getProjectTitle(project) }}</span>
@ -44,21 +53,25 @@
:class="{'is-favorite': project.isFavorite}"
@click="projectStore.toggleProjectFavorite(project)"
>
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']"/>
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']" />
</BaseButton>
<ProjectSettingsDropdown
v-if="project.id > 0"
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
class="menu-list-dropdown-trigger"
@click="toggleOpen"
>
<icon
icon="ellipsis-h"
class="icon"
/>
</BaseButton>
</template>
</ProjectSettingsDropdown>
<span class="list-setting-spacer" v-else></span>
</div>
<ProjectsNavigation
v-if="canNestDeeper && childProjectsOpen && canCollapse"
@ -147,6 +160,10 @@ const canNestDeeper = computed(() => canNestProjectDeeper(level))
}
}
.is-touch .color-bubble {
opacity: 1 !important;
}
.color-bubble-handle-wrapper {
position: relative;
width: 1rem;
@ -175,4 +192,8 @@ const canNestDeeper = computed(() => canNestProjectDeeper(level))
color: var(--grey-300) !important;
font-size: .75rem;
}
.is-touch .handle.has-color-bubble {
display: none !important;
}
</style>

View File

@ -1,69 +1,110 @@
<template>
<header :class="{ 'has-background': background, 'menu-active': menuActive }" aria-label="main navigation"
class="navbar d-print-none">
<router-link :to="{ name: 'home' }" class="logo-link">
<Logo width="164" height="48" />
<header
:class="{ 'has-background': background, 'menu-active': menuActive }"
aria-label="main navigation"
class="navbar d-print-none"
>
<router-link
:to="{ name: 'home' }"
class="logo-link"
>
<Logo
width="164"
height="48"
/>
</router-link>
<MenuButton class="menu-button" />
<div v-if="currentProject?.id" class="project-title-wrapper">
<div
v-if="currentProject?.id"
class="project-title-wrapper"
>
<h1 class="project-title">
{{ currentProject.title === '' ? $t('misc.loading') : getProjectTitle(currentProject) }}
</h1>
<BaseButton :to="{ name: 'project.info', params: { projectId: currentProject.id } }" class="project-title-button">
<BaseButton
:to="{ name: 'project.info', params: { projectId: currentProject.id } }"
class="project-title-button"
>
<icon icon="circle-info" />
</BaseButton>
<project-settings-dropdown v-if="canWriteCurrentProject && currentProject.id !== -1"
class="project-title-dropdown" :project="currentProject">
<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
class="project-title-button"
@click="toggleOpen"
>
<icon
icon="ellipsis-h"
class="icon"
/>
</BaseButton>
</template>
</project-settings-dropdown>
</ProjectSettingsDropdown>
</div>
<div class="navbar-end">
<BaseButton @click="openQuickActions" class="trigger-button" v-shortcut="'Control+k'"
:title="$t('keyboardShortcuts.quickSearch')">
<icon icon="search" />
</BaseButton>
<OpenQuickActions />
<Notifications />
<dropdown>
<Dropdown>
<template #trigger="{ toggleOpen, open }">
<BaseButton class="username-dropdown-trigger" @click="toggleOpen" variant="secondary" :shadow="false">
<img :src="authStore.avatarUrl" alt="" class="avatar" width="40" height="40" />
<BaseButton
class="username-dropdown-trigger"
variant="secondary"
:shadow="false"
@click="toggleOpen"
>
<img
:src="authStore.avatarUrl"
alt=""
class="avatar"
width="40"
height="40"
>
<span class="username">{{ authStore.userDisplayName }}</span>
<span class="icon is-small" :style="{
transform: open ? 'rotate(180deg)' : 'rotate(0)',
}">
<span
class="icon is-small"
:style="{
transform: open ? 'rotate(180deg)' : 'rotate(0)',
}"
>
<icon icon="chevron-down" />
</span>
</BaseButton>
</template>
<dropdown-item :to="{ name: 'user.settings' }">
<DropdownItem :to="{ name: 'user.settings' }">
{{ $t('user.settings.title') }}
</dropdown-item>
<dropdown-item v-if="imprintUrl" :href="imprintUrl">
</DropdownItem>
<DropdownItem
v-if="imprintUrl"
:href="imprintUrl"
>
{{ $t('navigation.imprint') }}
</dropdown-item>
<dropdown-item v-if="privacyPolicyUrl" :href="privacyPolicyUrl">
</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>
</DropdownItem>
</Dropdown>
</div>
</header>
</template>
@ -80,6 +121,7 @@ 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 { getProjectTitle } from '@/helpers/getProjectTitle'
@ -98,10 +140,6 @@ const authStore = useAuthStore()
const configStore = useConfigStore()
const imprintUrl = computed(() => configStore.legal.imprintUrl)
const privacyPolicyUrl = computed(() => configStore.legal.privacyPolicyUrl)
function openQuickActions() {
baseStore.setQuickActionsActive(true)
}
</script>
<style lang="scss" scoped>

View File

@ -1,11 +1,16 @@
<template>
<div class="update-notification" v-if="updateAvailable">
<p class="update-notification__message">{{ $t('update.available') }}</p>
<div
v-if="updateAvailable"
class="update-notification"
>
<p class="update-notification__message">
{{ $t('update.available') }}
</p>
<x-button
@click="refreshApp()"
:shadow="false"
:wrap="false"
>
@click="refreshApp()"
>
{{ $t('update.do') }}
</x-button>
</div>

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 }">
<router-view
v-slot="{ Component }"
:route="routeWithModal"
>
<keep-alive :include="['project.list', 'project.gantt', 'project.table', 'project.kanban']">
<component :is="Component"/>
<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>

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': currentProject?.title === '' ? '0': '1' }"
class="title">
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>

View File

@ -1,46 +1,70 @@
<template>
<aside :class="{'is-active': baseStore.menuActive}" class="menu-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>
<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: 'projects.index'}" v-shortcut="'g p'">
<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('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>
@ -53,13 +77,27 @@
variant="small"
/>
<template v-else>
<nav class="menu" v-if="favoriteProjects">
<nav
v-if="favoriteProjects"
class="menu"
>
<ProjectsNavigation
:model-value="favoriteProjects"
:can-edit-order="false"
:can-collapse="false"
/>
</nav>
<nav
v-if="savedFilterProjects"
class="menu"
>
<ProjectsNavigation
:model-value="savedFilterProjects"
:can-edit-order="false"
:can-collapse="false"
/>
</nav>
<nav class="menu">
<ProjectsNavigation
@ -71,7 +109,7 @@
</nav>
</template>
<PoweredByLink/>
<PoweredByLink />
</aside>
</template>
@ -91,6 +129,7 @@ const projectStore = useProjectStore()
const projects = computed(() => projectStore.notArchivedRootProjects)
const favoriteProjects = computed(() => projectStore.favoriteProjects)
const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
</script>
<style lang="scss" scoped>

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,36 +1,67 @@
<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 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>
>
<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"
/>
</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>
@ -39,6 +70,14 @@ 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,17 +92,18 @@ const lastChangeTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
const defaultColors = ref(DEFAULT_COLORS)
const colorListID = ref(createRandomID())
const {
modelValue,
} = defineProps<{
modelValue: string,
}>()
const emit = defineEmits(['update:modelValue'])
watch(
() => modelValue,
(newValue) => {
if (newValue === '' || newValue.startsWith('var(')) {
color.value = ''
return
}
if (!newValue.startsWith('#') && (newValue.length === 6 || newValue.length === 3)) {
newValue = `#${newValue}`
}
color.value = newValue
},
{immediate: true},

View File

@ -1,5 +1,5 @@
<template>
<multiselect
<Multiselect
v-model="selectedProjects"
:search-results="foundProjects"
:loading="projectService.loading"
@ -20,11 +20,20 @@ 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
@ -58,6 +67,8 @@ async function findProjects(query: string) {
const response = await projectService.getAll({}, {s: query}) as IProject[]
// Filter selected items from the results
foundProjects.value = response.filter(({id}) => !includesById(projects.value, id))
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

@ -1,6 +1,6 @@
<template>
<BaseButton class="simple-button">
<slot/>
<slot />
</BaseButton>
</template>

View File

@ -18,7 +18,10 @@
:icon="icon"
: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 : undefined}"
@ -38,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">

View File

@ -1,22 +1,29 @@
<template>
<div class="datepicker">
<SimpleButton @click.stop="toggleDatePopup" class="show" :disabled="disabled || undefined">
<SimpleButton
class="show"
:disabled="disabled || undefined"
@click.stop="toggleDatePopup"
>
{{ date === null ? chooseDateLabel : formatDateShort(date) }}
</SimpleButton>
<CustomTransition name="fade">
<div v-if="show" class="datepicker-popup" ref="datepickerPopup">
<div
v-if="show"
ref="datepickerPopup"
class="datepicker-popup"
>
<DatepickerInline
v-model="date"
@update:model-value="updateData"
@update:modelValue="updateData"
/>
<x-button
v-cy="'closeDatepicker'"
class="datepicker__close-button"
:shadow="false"
@click="close"
v-cy="'closeDatepicker'"
>
{{ $t('misc.confirm') }}
</x-button>
@ -56,7 +63,7 @@ const props = defineProps({
},
})
const emit = defineEmits(['update:modelValue', 'close', 'close-on-change'])
const emit = defineEmits(['update:modelValue', 'close', 'closeOnChange'])
const date = ref<Date | null>()
const show = ref(false)
@ -108,7 +115,7 @@ function close() {
emit('close', changed.value)
if (changed.value) {
changed.value = false
emit('close-on-change', changed.value)
emit('closeOnChange', changed.value)
}
}, 200)
}

View File

@ -4,7 +4,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('today')"
>
<span class="icon"><icon :icon="['far', 'calendar-alt']"/></span>
<span class="icon"><icon :icon="['far', 'calendar-alt']" /></span>
<span class="text">
<span>{{ $t('input.datepicker.today') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('today') }}</span>
@ -14,7 +14,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('tomorrow')"
>
<span class="icon"><icon :icon="['far', 'sun']"/></span>
<span class="icon"><icon :icon="['far', 'sun']" /></span>
<span class="text">
<span>{{ $t('input.datepicker.tomorrow') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('tomorrow') }}</span>
@ -24,7 +24,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('nextMonday')"
>
<span class="icon"><icon icon="coffee"/></span>
<span class="icon"><icon icon="coffee" /></span>
<span class="text">
<span>{{ $t('input.datepicker.nextMonday') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('nextMonday') }}</span>
@ -34,7 +34,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('thisWeekend')"
>
<span class="icon"><icon icon="cocktail"/></span>
<span class="icon"><icon icon="cocktail" /></span>
<span class="text">
<span>{{ $t('input.datepicker.thisWeekend') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('thisWeekend') }}</span>
@ -44,7 +44,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('laterThisWeek')"
>
<span class="icon"><icon icon="chess-knight"/></span>
<span class="icon"><icon icon="chess-knight" /></span>
<span class="text">
<span>{{ $t('input.datepicker.laterThisWeek') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('laterThisWeek') }}</span>
@ -54,7 +54,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('nextWeek')"
>
<span class="icon"><icon icon="forward"/></span>
<span class="icon"><icon icon="forward" /></span>
<span class="text">
<span>{{ $t('input.datepicker.nextWeek') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('nextWeek') }}</span>
@ -63,8 +63,8 @@
<div class="flatpickr-container">
<flat-pickr
:config="flatPickerConfig"
v-model="flatPickrDate"
:config="flatPickerConfig"
/>
</div>
</template>
@ -80,8 +80,8 @@ import {formatDate} from '@/helpers/time/formatDate'
import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {useAuthStore} from '@/stores/auth'
import {useI18n} from 'vue-i18n'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const props = defineProps({
modelValue: {
@ -105,8 +105,6 @@ watch(
{immediate: true},
)
const authStore = useAuthStore()
const weekStart = computed(() => authStore.settings.weekStart)
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,
@ -114,9 +112,7 @@ const flatPickerConfig = computed(() => ({
enableTime: true,
time_24hr: true,
inline: true,
locale: {
firstDayOfWeek: weekStart.value,
},
locale: getFlatpickrLanguage(),
}))
// Since flatpickr dates are strings, we need to convert them to native date objects.
@ -128,6 +124,12 @@ const flatPickrDate = computed({
return
}
if (date.value !== null) {
const oldDate = formatDate(date.value, 'yyy-LL-dd H:mm')
if (oldDate === newValue) {
return
}
}
date.value = createDateFromString(newValue)
updateData()
},
@ -155,10 +157,6 @@ function updateData() {
}
function setDate(dateString: string) {
if (date.value === null) {
date.value = new Date()
}
const interval = calculateDayInterval(dateString)
const newDate = new Date()
newDate.setDate(newDate.getDate() + interval)
@ -166,7 +164,6 @@ function setDate(dateString: string) {
newDate.setMinutes(0)
newDate.setSeconds(0)
date.value = newDate
flatPickrDate.value = newDate
updateData()
}

View File

@ -1,444 +0,0 @@
<template>
<div class="editor">
<div class="clear"></div>
<vue-easymde
:configs="config"
@change="() => bubbleNow()"
@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="bubbleSaveClick"
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="bubbleSaveClick"
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', 'save'])
const text = ref('')
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
}
bubbleNow()
},
)
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
bubbleNow()
}
function bubbleNow() {
emit('update:modelValue', text.value)
}
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, {
mangle: false,
headerIds: false,
}), {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 projectPrefix = text.value.substring(index, index + 1)
console.debug({index, projectPrefix, checked, text: text.value})
text.value = replaceAt(text.value, index, `${projectPrefix} ${checked ? '[x]' : '[ ]'} `)
bubbleNow()
emit('save', text.value)
renderPreview()
}
function toggleEdit() {
isPreviewActive.value = false
isEditActive.value = true
}
function bubbleSaveClick() {
isPreviewActive.value = true
isEditActive.value = false
renderPreview()
bubbleNow()
emit('save', text.value)
}
</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,
}),
]
},
})

View File

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

View File

@ -0,0 +1,214 @@
import {VueRenderer} from '@tiptap/vue-3'
import tippy from 'tippy.js'
import CommandsList from './CommandsList.vue'
export default function suggestionSetup(t) {
return {
items: ({query}: { query: string }) => {
return [
{
title: t('input.editor.text'),
description: t('input.editor.textTooltip'),
icon: 'fa-font',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.setNode('paragraph', {level: 1})
.run()
},
},
{
title: t('input.editor.heading1'),
description: t('input.editor.heading1Tooltip'),
icon: 'fa-header',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.setNode('heading', {level: 1})
.run()
},
},
{
title: t('input.editor.heading2'),
description: t('input.editor.heading2Tooltip'),
icon: 'fa-header',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.setNode('heading', {level: 2})
.run()
},
},
{
title: t('input.editor.heading3'),
description: t('input.editor.heading3Tooltip'),
icon: 'fa-header',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.setNode('heading', {level: 2})
.run()
},
},
{
title: t('input.editor.bulletList'),
description: t('input.editor.bulletListTooltip'),
icon: 'fa-list-ul',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.toggleBulletList()
.run()
},
},
{
title: t('input.editor.orderedList'),
description: t('input.editor.orderedListTooltip'),
icon: 'fa-list-ol',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.toggleOrderedList()
.run()
},
},
{
title: t('input.editor.taskList'),
description: t('input.editor.taskListTooltip'),
icon: 'fa-list-check',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.toggleTaskList()
.run()
},
},
{
title: t('input.editor.quote'),
description: t('input.editor.quoteTooltip'),
icon: 'fa-quote-right',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.toggleBlockquote()
.run()
},
},
{
title: t('input.editor.code'),
description: t('input.editor.codeTooltip'),
icon: 'fa-code',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.toggleCodeBlock()
.run()
},
},
{
title: t('input.editor.image'),
description: t('input.editor.imageTooltip'),
icon: 'fa-image',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.run()
document.getElementById('tiptap__image-upload').click()
},
},
{
title: t('input.editor.horizontalRule'),
description: t('input.editor.horizontalRuleTooltip'),
icon: 'fa-ruler-horizontal',
command: ({editor, range}) => {
editor
.chain()
.focus()
.deleteRange(range)
.setHorizontalRule()
.run()
},
},
].filter(item => item.title.toLowerCase().startsWith(query.toLowerCase()))
},
render: () => {
let component: VueRenderer
let popup
return {
onStart: props => {
component = new VueRenderer(CommandsList, {
// using vue 2:
// parent: this,
// propsData: props,
props,
editor: props.editor,
})
if (!props.clientRect) {
return
}
popup = tippy('body', {
getReferenceClientRect: props.clientRect,
appendTo: () => document.body,
content: component.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
})
},
onUpdate(props) {
component.updateProps(props)
if (!props.clientRect) {
return
}
popup[0].setProps({
getReferenceClientRect: props.clientRect,
})
},
onKeyDown(props) {
if (props.event.key === 'Escape') {
popup[0].hide()
return true
}
return component.ref?.onKeyDown(props)
},
onExit() {
popup[0].destroy()
component.destroy()
},
}
},
}
}

View File

@ -0,0 +1,6 @@
export type UploadCallback = (files: File[] | FileList) => Promise<string[]>
export interface BottomAction {
title: string
action: () => void,
}

View File

@ -1,135 +0,0 @@
import EasyMDE from 'easymde'
import {i18n} from '@/i18n'
export function createEasyMDEConfig({ placeholder, uploadImage, imageUploadFunction }) {
return {
autoDownloadFontAwesome: false,
spellChecker: false,
placeholder,
uploadImage,
imageUploadFunction,
minHeight: '150px',
sideBySideFullscreen: false,
toolbar: [
{
name: 'heading-1',
action: EasyMDE.toggleHeading1,
title: i18n.global.t('input.editor.heading1'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
{
name: 'heading-2',
action: EasyMDE.toggleHeading2,
title: i18n.global.t('input.editor.heading2'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
{
name: 'heading-3',
action: EasyMDE.toggleHeading3,
title: i18n.global.t('input.editor.heading3'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
{
name: 'heading-smaller',
action: EasyMDE.toggleHeadingSmaller,
title: i18n.global.t('input.editor.headingSmaller'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
{
name: 'heading-bigger',
action: EasyMDE.toggleHeadingBigger,
title: i18n.global.t('input.editor.headingBigger'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
'|',
{
name: 'bold',
action: EasyMDE.toggleBold,
title: i18n.global.t('input.editor.bold'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M3.5 3H6.5H15.25C18.15 3 20.5 5.36 20.5 8.25C20.5 9.8 19.81 11.19 18.73 12.15C20.37 13.04 21.5 14.76 21.5 16.75C21.5 19.64 19.15 22 16.25 22H6.5H3.5C2.95 22 2.5 21.55 2.5 21C2.5 20.45 2.95 20 3.5 20H5.5V5H3.5C2.95 5 2.5 4.55 2.5 4C2.5 3.45 2.95 3 3.5 3ZM7.5 20H16.25C18.04 20 19.5 18.54 19.5 16.75C19.5 14.96 18.04 13.5 16.25 13.5H7.5V20ZM7.5 11.5H15.25C17.04 11.5 18.5 10.04 18.5 8.25C18.5 6.46 17.04 5 15.25 5H7.5V11.5Z"/></svg>',
},
{
name: 'italic',
action: EasyMDE.toggleItalic,
title: i18n.global.t('input.editor.italic'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M14.0967 4.2H17.0001C17.3301 4.2 17.6001 3.93 17.6001 3.6C17.6001 3.27 17.3301 3 17.0001 3H10.2001C9.8701 3 9.6001 3.27 9.6001 3.6C9.6001 3.93 9.8701 4.2 10.2001 4.2H12.8748L9.90335 19.8H6.9999C6.6699 19.8 6.3999 20.07 6.3999 20.4C6.3999 20.73 6.6699 21 6.9999 21H13.7999C14.1299 21 14.3999 20.73 14.3999 20.4C14.3999 20.07 14.1299 19.8 13.7999 19.8H11.1253L14.0967 4.2Z"/></svg>',
},
{
name: 'strikethrough',
action: EasyMDE.toggleStrikethrough,
title: i18n.global.t('input.editor.strikethrough'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M18.25 7.17005C18.25 7.50005 17.98 7.77005 17.65 7.77005C17.32 7.77005 17.05 7.50005 17.05 7.17005V5.96005C15.97 5.12005 14.17 4.56005 12.79 4.31005C11.1 4.00005 9.51 4.30005 8.41 5.12005C7.2 6.03005 6.67 7.67005 7.19 8.88005C7.56 9.73005 8.37 10.31 8.98 10.64C9.57215 10.9644 10.1961 11.2013 10.8465 11.3999H20.4C20.73 11.3999 21 11.6699 21 11.9999C21 12.3299 20.73 12.5999 20.4 12.5999H15.3012C16.6583 13.0929 17.5255 13.7765 17.95 14.69C18.73 16.36 17.74 18.33 16.36 19.41C15.05 20.4401 13.35 21 11.54 21H11.16C9.78 20.9401 8.34 20.5301 6.95 19.85V20.3601C6.95 20.6901 6.68 20.96 6.35 20.96C6.02 20.96 5.75 20.6901 5.75 20.3601V17.36C5.75 17.03 6.02 16.76 6.35 16.76C6.68 16.76 6.95 17.03 6.95 17.36V18.5C8.35 19.2801 9.81 19.74 11.21 19.8C12.86 19.89 14.46 19.39 15.62 18.48C16.6 17.71 17.37 16.3 16.86 15.21C16.55 14.54 15.8 14.0201 14.58 13.63C13.9711 13.4331 13.3222 13.2762 12.6906 13.1235C12.6168 13.1056 12.5432 13.0878 12.47 13.07C12.4313 13.0607 12.3925 13.0514 12.3537 13.0421C11.7861 12.9055 11.2108 12.767 10.6413 12.5999H3.6C3.27 12.5999 3 12.3299 3 11.9999C3 11.6699 3.27 11.3999 3.6 11.3999H7.90288C7.04984 10.8343 6.42752 10.1363 6.09 9.36005C5.34 7.63005 6.03 5.40005 7.69 4.16005C9.05 3.15005 10.99 2.77005 13 3.13005C13.64 3.25005 15.53 3.66005 17.05 4.53005V4.17005C17.05 3.84005 17.32 3.57005 17.65 3.57005C17.98 3.57005 18.25 3.84005 18.25 4.17005V7.17005Z"/></svg>',
},
{
name: 'code',
action: EasyMDE.toggleCodeBlock,
title: i18n.global.t('input.editor.code'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M8.57 20.9601C8.64 20.9901 8.71 21.0001 8.78 21.0001C9.02 21.0001 9.24 20.8501 9.34 20.6101L15.79 3.81005C15.9 3.50005 15.75 3.15005 15.44 3.03005C15.14 2.92005 14.79 3.07005 14.67 3.38005L8.22 20.1801C8.11 20.4901 8.26 20.8401 8.57 20.9601ZM7.00007 18.0001C6.85007 18.0001 6.69007 17.9401 6.58007 17.8201L1.18007 12.4201C0.950068 12.1901 0.950068 11.8101 1.18007 11.5701L6.58007 6.17006C6.81007 5.94006 7.19007 5.94006 7.43007 6.17006C7.66007 6.40006 7.66007 6.78006 7.43007 7.02006L2.45007 12.0001L7.43007 16.9801C7.66007 17.2101 7.66007 17.5901 7.43007 17.8301C7.31007 17.9401 7.15007 18.0001 7.00007 18.0001ZM17 18.0001C16.85 18.0001 16.69 17.9401 16.58 17.8201C16.35 17.5901 16.35 17.2101 16.58 16.9701L21.55 12.0001L16.57 7.02006C16.34 6.79006 16.34 6.41006 16.57 6.17006C16.81 5.94006 17.19 5.94006 17.42 6.17006L22.82 11.5701C23.05 11.8001 23.05 12.1801 22.82 12.4201L17.42 17.8201C17.31 17.9401 17.15 18.0001 17 18.0001Z"/></svg>',
},
{
name: 'quote',
action: EasyMDE.toggleBlockquote,
title: i18n.global.t('input.editor.quote'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M19.373 5.16357H5.62695C4.79102 5.16357 4.11133 5.84326 4.11133 6.6792V16.2095C4.11133 17.0464 4.79102 17.7261 5.62695 17.7261H6.8877V21.1245C6.8877 21.3667 7.0332 21.5854 7.25684 21.6782C7.33203 21.7095 7.41016 21.7241 7.4873 21.7241C7.64258 21.7241 7.7959 21.6636 7.91113 21.5493L11.748 17.7261H19.373C20.209 17.7261 20.8887 17.0464 20.8887 16.2095V6.6792C20.8887 5.84326 20.209 5.16357 19.373 5.16357ZM19.6895 16.2095C19.6895 16.3843 19.5469 16.5269 19.373 16.5269H11.5C11.3408 16.5269 11.1895 16.5894 11.0762 16.7017L8.08691 19.6802V17.1265C8.08691 16.7954 7.81836 16.5269 7.4873 16.5269H5.62695C5.45312 16.5269 5.31055 16.3843 5.31055 16.2095V6.6792C5.31055 6.50537 5.45312 6.36279 5.62695 6.36279H19.373C19.5469 6.36279 19.6895 6.50537 19.6895 6.6792V16.2095ZM10.3431 8.45264C9.46326 8.45264 8.75 9.16589 8.75 10.0458C8.75 10.9257 9.46326 11.639 10.3431 11.639C10.4775 11.639 10.6058 11.6173 10.7305 11.5861V11.6195C10.7305 12.1322 10.3135 12.5492 9.75586 12.5492C9.4248 12.5492 9.17871 12.8177 9.17871 13.1488C9.17871 13.4799 9.46973 13.7484 9.80078 13.7484C10.9746 13.7484 11.9297 12.7933 11.9297 11.6195V10.1176L11.9294 10.1165L11.9292 10.1155C11.9297 10.1049 11.9312 10.0946 11.9326 10.0843L11.9326 10.0843C11.9345 10.0716 11.9363 10.059 11.9363 10.0458C11.9363 9.16589 11.223 8.45264 10.3431 8.45264ZM13.0637 10.0458C13.0637 9.16589 13.7771 8.45264 14.657 8.45264C15.5369 8.45264 16.2501 9.16589 16.2501 10.0458C16.2501 10.0584 16.2484 10.0706 16.2466 10.0828C16.2452 10.0929 16.2437 10.103 16.2433 10.1134C16.2433 10.1149 16.2441 10.1161 16.2441 10.1176V11.6195C16.2441 12.7933 15.2891 13.7484 14.1152 13.7484C13.7842 13.7484 13.4922 13.4799 13.4922 13.1488C13.4922 12.8177 13.7383 12.5492 14.0693 12.5492C14.6279 12.5492 15.0449 12.1322 15.0449 11.6195V11.5858C14.9202 11.6173 14.7915 11.639 14.657 11.639C13.7771 11.639 13.0637 10.9257 13.0637 10.0458Z"/></svg>',
},
{
name: 'unordered-list',
action: EasyMDE.toggleUnorderedList,
title: i18n.global.t('input.editor.unorderedList'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M6.5281 3.7002H3.5281C3.1981 3.7002 2.9281 3.9702 2.9281 4.3002V7.3002C2.9281 7.6302 3.1981 7.9002 3.5281 7.9002H6.5281C6.8581 7.9002 7.1281 7.6302 7.1281 7.3002V4.3002C7.1281 3.9702 6.8581 3.7002 6.5281 3.7002ZM5.9281 6.7002H4.1281V4.9002H5.9281V6.7002ZM3.5281 9.90015H6.5281C6.8581 9.90015 7.1281 10.1701 7.1281 10.5001V13.5001C7.1281 13.8301 6.8581 14.1001 6.5281 14.1001H3.5281C3.1981 14.1001 2.9281 13.8301 2.9281 13.5001V10.5001C2.9281 10.1701 3.1981 9.90015 3.5281 9.90015ZM4.1281 12.9001H5.9281V11.1001H4.1281V12.9001ZM3.5281 16.1001H6.5281C6.8581 16.1001 7.1281 16.3701 7.1281 16.7001V19.7001C7.1281 20.0301 6.8581 20.3001 6.5281 20.3001H3.5281C3.1981 20.3001 2.9281 20.0301 2.9281 19.7001V16.7001C2.9281 16.3701 3.1981 16.1001 3.5281 16.1001ZM4.1281 19.1001H5.9281V17.3001H4.1281V19.1001ZM9.72817 6.4002H20.7282C21.0582 6.4002 21.3282 6.1302 21.3282 5.8002C21.3282 5.4702 21.0582 5.2002 20.7282 5.2002H9.72817C9.39817 5.2002 9.12817 5.4702 9.12817 5.8002C9.12817 6.1302 9.39817 6.4002 9.72817 6.4002ZM9.72817 11.4001H20.7282C21.0582 11.4001 21.3282 11.6701 21.3282 12.0001C21.3282 12.3301 21.0582 12.6001 20.7282 12.6001H9.72817C9.39817 12.6001 9.12817 12.3301 9.12817 12.0001C9.12817 11.6701 9.39817 11.4001 9.72817 11.4001ZM9.72817 17.6001H20.7282C21.0582 17.6001 21.3282 17.8701 21.3282 18.2001C21.3282 18.5301 21.0582 18.8001 20.7282 18.8001H9.72817C9.39817 18.8001 9.12817 18.5301 9.12817 18.2001C9.12817 17.8701 9.39817 17.6001 9.72817 17.6001Z"/></svg>',
},
{
name: 'ordered-list',
action: EasyMDE.toggleOrderedList,
title: i18n.global.t('input.editor.orderedList'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M4.19494 8.29994H5.99494C6.26494 8.29994 6.49494 8.07995 6.49494 7.79994C6.49494 7.51995 6.27494 7.29994 5.99494 7.29994H5.59494V3.79994C5.59494 3.62994 5.50494 3.46994 5.36494 3.37994C5.22494 3.28994 5.04494 3.26994 4.89494 3.33994L3.89494 3.76994C3.64494 3.87994 3.52494 4.17994 3.63494 4.42994C3.74494 4.67994 4.03494 4.79994 4.29494 4.68994L4.59494 4.55994V7.29994H4.19494C3.91494 7.29994 3.69494 7.51995 3.69494 7.79994C3.69494 8.07995 3.91494 8.29994 4.19494 8.29994ZM20.195 6.39995H9.19497C8.86497 6.39995 8.59497 6.12995 8.59497 5.79995C8.59497 5.46995 8.86497 5.19995 9.19497 5.19995H20.195C20.525 5.19995 20.795 5.46995 20.795 5.79995C20.795 6.12995 20.525 6.39995 20.195 6.39995ZM3.78486 14.36H6.37486C6.65486 14.36 6.87486 14.14 6.87486 13.86C6.87486 13.58 6.65486 13.36 6.37486 13.36H4.88486C5.00486 13.23 5.12486 13.09 5.23486 12.95C5.26626 12.9151 5.29645 12.8802 5.32626 12.8458L5.32629 12.8457C5.38192 12.7814 5.43627 12.7186 5.49486 12.66C5.73486 12.4 5.98486 12.12 6.17486 11.79C6.47486 11.25 6.41486 10.63 6.01486 10.17C5.57486 9.66 4.86486 9.5 4.24486 9.74C3.74486 9.95 3.39486 10.35 3.22486 10.91C3.14486 11.18 3.29486 11.46 3.56486 11.54C3.82486 11.61 4.10486 11.46 4.18486 11.2C4.29486 10.85 4.48486 10.73 4.62486 10.67C4.88486 10.57 5.13486 10.68 5.26486 10.82C5.38486 10.96 5.40486 11.12 5.30486 11.29C5.17595 11.5202 4.99618 11.7165 4.80458 11.9257L4.75486 11.98C4.67298 12.0801 4.58283 12.1801 4.49946 12.2727L4.49945 12.2727L4.47486 12.3C4.12486 12.72 3.76486 13.13 3.40486 13.53C3.27486 13.68 3.23486 13.9 3.32486 14.07C3.41486 14.24 3.58486 14.36 3.78486 14.36ZM3.68486 20.3699C4.04486 20.5899 4.46486 20.6999 4.87486 20.6999C5.13486 20.6999 5.38486 20.6499 5.61486 20.5499C6.31486 20.2799 6.73486 19.5599 6.60486 18.8799C6.53486 18.5499 6.35486 18.2899 6.12486 18.0899C6.32486 17.8999 6.45486 17.6499 6.50486 17.3799C6.57486 17.0099 6.49486 16.6299 6.27486 16.3099C5.85486 15.6899 5.07486 15.5199 4.10486 15.8299C3.83486 15.9199 3.69486 16.1999 3.77486 16.4599C3.86486 16.7299 4.14486 16.8699 4.40486 16.7899C4.70486 16.6999 5.24486 16.5799 5.45486 16.8899C5.51486 16.9899 5.54486 17.0999 5.52486 17.1999C5.51486 17.2699 5.47486 17.3599 5.36486 17.4299C5.26486 17.4999 5.12486 17.5399 4.95486 17.5799L4.77486 17.6299C4.54486 17.6999 4.40486 17.9099 4.41486 18.1499C4.42486 18.3899 4.61486 18.5799 4.84486 18.6099C5.20486 18.6599 5.58486 18.8299 5.63486 19.0799C5.67486 19.2999 5.46486 19.5499 5.25486 19.6299C4.94486 19.7599 4.52486 19.7099 4.21486 19.5199C3.97486 19.3699 3.67486 19.4399 3.52486 19.6799C3.37486 19.9199 3.44486 20.2299 3.68486 20.3699ZM20.195 18.7999H9.19497C8.86497 18.7999 8.59497 18.5299 8.59497 18.1999C8.59497 17.8699 8.86497 17.5999 9.19497 17.5999H20.195C20.525 17.5999 20.795 17.8699 20.795 18.1999C20.795 18.5299 20.525 18.7999 20.195 18.7999ZM9.19497 12.5999H20.195C20.525 12.5999 20.795 12.3299 20.795 11.9999C20.795 11.6699 20.525 11.3999 20.195 11.3999H9.19497C8.86497 11.3999 8.59497 11.6699 8.59497 11.9999C8.59497 12.3299 8.86497 12.5999 9.19497 12.5999Z"/></svg>',
},
'|',
{
name: 'clean-block',
action: EasyMDE.cleanBlock,
title: i18n.global.t('input.editor.cleanBlock'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M9.25989 6.18384H20.4513C20.7823 6.18384 21.0509 6.45239 21.0509 6.78345V17.9749C21.0509 18.3059 20.7823 18.5745 20.4513 18.5745H9.25989C9.0929 18.5745 8.93469 18.5061 8.82043 18.384L3.6095 12.7883C3.39563 12.5579 3.39563 12.2004 3.6095 11.97L8.82043 6.37427C8.93469 6.2522 9.0929 6.18384 9.25989 6.18384ZM9.52063 17.3752H19.8517V7.38306H9.52063L4.86926 12.3792L9.52063 17.3752ZM12.7755 15.0686C12.6222 15.0686 12.4679 15.01 12.3517 14.8928C12.1173 14.6584 12.1173 14.2786 12.3517 14.0452L14.0503 12.3469L12.3517 10.6487C12.1173 10.4153 12.1173 10.0354 12.3517 9.80103C12.5841 9.56665 12.965 9.56665 13.1993 9.80103L14.8981 11.4994L16.5968 9.80103C16.8312 9.56665 17.212 9.56665 17.4445 9.80103C17.6788 10.0354 17.6788 10.4153 17.4445 10.6487L15.7458 12.3469L17.4445 14.0452C17.6788 14.2786 17.6788 14.6584 17.4445 14.8928C17.3282 15.01 17.174 15.0686 17.0206 15.0686C16.8673 15.0686 16.714 15.01 16.5968 14.8928L14.8981 13.1945L13.1993 14.8928C13.0822 15.01 12.9288 15.0686 12.7755 15.0686Z"/></svg>',
},
{
name: 'link',
action: EasyMDE.drawLink,
title: i18n.global.t('input.editor.link'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M11.4399 15.3452C11.4999 15.3652 11.5699 15.3752 11.6299 15.3752C11.8799 15.3752 12.1199 15.2152 12.1999 14.9652C12.2999 14.6452 12.1299 14.3052 11.8199 14.2052C11.3499 14.0452 10.9299 13.7852 10.5699 13.4152C10.1999 13.0552 9.9399 12.6452 9.7799 12.1552C9.6599 11.8252 9.5999 11.4652 9.5999 11.0952C9.5999 10.2152 9.9399 9.38518 10.5699 8.75518L15.1599 4.15518C16.4499 2.87518 18.5399 2.87518 19.8299 4.15518C20.4499 4.78518 20.7899 5.61518 20.7899 6.49518C20.7899 7.37518 20.4499 8.20518 19.8299 8.82518L16.7399 11.9052C16.5099 12.1452 16.5099 12.5252 16.7399 12.7552C16.9799 12.9852 17.3599 12.9852 17.5899 12.7552L20.6799 9.67518C21.5299 8.83518 21.9999 7.69518 21.9999 6.49518C21.9999 5.29518 21.5299 4.16518 20.6899 3.30518C18.9299 1.55518 16.0799 1.55518 14.3199 3.30518L9.7299 7.90518C8.8699 8.75518 8.3999 9.88518 8.3999 11.0952C8.3999 11.6152 8.4899 12.1152 8.6499 12.5552C8.8599 13.1952 9.2399 13.7952 9.7199 14.2652C10.1999 14.7552 10.7999 15.1352 11.4399 15.3452ZM3.32 20.6851C4.2 21.5551 5.35 21.9951 6.5 21.9951C7.65 21.9951 8.81 21.5551 9.69 20.7051L14.28 16.1051C15.14 15.2551 15.61 14.1251 15.61 12.9151C15.61 12.4551 15.54 11.9951 15.4 11.5551C15.17 10.8651 14.8 10.2551 14.28 9.73509C13.76 9.21509 13.15 8.84509 12.46 8.61509C12.14 8.51509 11.8 8.68509 11.7 8.99509C11.6 9.30509 11.77 9.64509 12.1 9.75509C12.61 9.91509 13.06 10.1951 13.44 10.5751C13.82 10.9551 14.09 11.4051 14.26 11.9151C14.36 12.2351 14.41 12.5651 14.41 12.9051C14.41 13.7951 14.06 14.6251 13.43 15.2451L8.84 19.8451C7.55 21.1251 5.46 21.1251 4.17 19.8451C3.55 19.2151 3.21 18.3951 3.21 17.5051C3.21 16.6151 3.55 15.7851 4.17 15.1651L7.35 11.9851C7.58 11.7451 7.59 11.3651 7.35 11.1351C7.11 10.9051 6.73 10.9051 6.5 11.1351L3.32 14.3151C2.47 15.1551 2 16.2851 2 17.4951C2 18.7051 2.47 19.8351 3.32 20.6851Z"/></svg>',
},
{
name: 'image',
action: EasyMDE.drawImage,
title: i18n.global.t('input.editor.image'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M4 4C2.89543 4 2 4.89543 2 6V16V17.5152V18C2 19.1046 2.89543 20 4 20H20C21.0528 20 21.9156 19.1866 21.9942 18.1539L22 18.1632V18V16V6C22 4.89543 21.1046 4 20 4H4ZM3.2 17.7V16.5642L6.78192 13.7254C6.8616 13.6622 6.97597 13.6689 7.04776 13.7409L10.3126 17.0146C10.7026 17.4056 11.3357 17.4065 11.7268 17.0165C11.7606 16.9827 11.792 16.9465 11.8207 16.9083L16.736 10.352C16.8023 10.2636 16.9277 10.2457 17.016 10.312C17.0355 10.3265 17.0521 10.3445 17.0651 10.365L20.8 16.2669V17.7C20.8 18.3075 20.3075 18.8 19.7 18.8H4.3C3.69249 18.8 3.2 18.3075 3.2 17.7ZM17.3865 8.61836L20.8 14.08V6.3C20.8 5.69249 20.3075 5.2 19.7 5.2H4.3C3.69249 5.2 3.2 5.69249 3.2 6.3V15.04L6.65054 12.2796C6.84949 12.1204 7.13629 12.1363 7.31645 12.3164L10.8369 15.8369C10.915 15.915 11.0417 15.915 11.1198 15.8369C11.1265 15.8302 16.5625 8.58336 16.5625 8.58336C16.7282 8.36245 17.0416 8.31768 17.2625 8.48336C17.3118 8.52034 17.3538 8.56611 17.3865 8.61836ZM8 8.5C8 9.32843 7.32843 10 6.5 10C5.67157 10 5 9.32843 5 8.5C5 7.67157 5.67157 7 6.5 7C7.32843 7 8 7.67157 8 8.5Z"/></svg>',
},
{
name: 'table',
action: EasyMDE.drawTable,
title: i18n.global.t('input.editor.table'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M6.18524 3.08496H19.4152C20.6752 3.08496 21.7152 4.11496 21.7152 5.38496V18.615C21.7152 19.885 20.6852 20.915 19.4152 20.915H6.18524C4.91524 20.915 3.88525 19.885 3.88525 18.615V5.38496C3.88525 4.11496 4.91524 3.08496 6.18524 3.08496ZM19.4052 19.705C20.0152 19.705 20.5052 19.215 20.5052 18.605H20.5153V5.38496C20.5153 4.77496 20.0252 4.28496 19.4152 4.28496H6.18524C5.57524 4.28496 5.08521 4.77496 5.08521 5.38496V18.605C5.08521 19.215 5.57524 19.705 6.18524 19.705H19.4052ZM17.4453 9.15503H8.15527C7.82527 9.15503 7.5553 9.42503 7.5553 9.75503C7.5553 10.085 7.82527 10.355 8.15527 10.355H17.4453C17.7753 10.355 18.0453 10.085 18.0453 9.75503C18.0453 9.42503 17.7753 9.15503 17.4453 9.15503ZM17.4453 13.635H8.15527C7.82527 13.635 7.5553 13.905 7.5553 14.235C7.5553 14.565 7.82527 14.835 8.15527 14.835H17.4453C17.7753 14.835 18.0453 14.565 18.0453 14.235C18.0453 13.905 17.7753 13.635 17.4453 13.635Z"/></svg>',
},
{
name: 'horizontal-rule',
action: EasyMDE.drawHorizontalRule,
title: i18n.global.t('input.editor.horizontalRule'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M21 13H3C2.45 13 2 12.55 2 12C2 11.45 2.45 11 3 11H21C21.55 11 22 11.45 22 12C22 12.55 21.55 13 21 13Z"/></svg>',
},
'|',
{
name: 'side-by-side',
action: EasyMDE.toggleSideBySide,
title: i18n.global.t('input.editor.sideBySide'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M18.4787 14.58C18.3587 14.69 18.2987 14.85 18.2987 15C18.2987 15.15 18.3587 15.31 18.4787 15.42C18.7187 15.65 19.0987 15.65 19.3287 15.42L22.3287 12.42C22.5587 12.18 22.5587 11.8 22.3287 11.57L19.3287 8.56996C19.0887 8.33996 18.7087 8.33996 18.4787 8.56996C18.2487 8.80996 18.2487 9.18996 18.4787 9.41996L20.451 11.3999L14.4487 11.3999L14.4487 4.6C14.4487 4.27 14.1787 4 13.8487 4C13.5187 4 13.2487 4.27 13.2487 4.6L13.2487 19.4C13.2487 19.73 13.5187 20 13.8487 20C14.1787 20 14.4487 19.73 14.4487 19.4L14.4487 12.5999L20.4511 12.5999L18.4787 14.58ZM9.54878 19.4L9.54878 12.5999L3.5486 12.5999L5.52867 14.58C5.75867 14.81 5.75867 15.19 5.52867 15.43C5.29867 15.66 4.91867 15.66 4.67867 15.43L1.67867 12.43C1.63274 12.384 1.5956 12.3323 1.56725 12.2774C1.53058 12.2077 1.50724 12.1299 1.50068 12.0477C1.49934 12.0317 1.49867 12.0158 1.49867 12C1.49867 11.9841 1.49933 11.9682 1.50067 11.9522C1.51454 11.778 1.60365 11.6242 1.73526 11.5234L4.67867 8.57997C4.90867 8.34997 5.28867 8.34997 5.52867 8.57997C5.75867 8.80997 5.75867 9.18997 5.52867 9.42997L3.55107 11.3999L9.54878 11.3999L9.54878 4.6C9.54878 4.27 9.81878 4 10.1488 4C10.4788 4 10.7488 4.27 10.7488 4.6L10.7488 11.9999L10.7488 19.4C10.7488 19.73 10.4788 20 10.1488 20C9.81878 20 9.54878 19.73 9.54878 19.4Z"/></svg>',
},
{
name: 'guide',
action() {
window.open('https://www.markdownguide.org/basic-syntax/', '_blank')
},
title: i18n.global.t('input.editor.guide'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M19.4999 2.3999H6.4999C5.0699 2.3999 3.8999 3.5699 3.8999 4.9999V18.9999C3.8999 20.4299 5.0699 21.5999 6.4999 21.5999H19.4999C19.8299 21.5999 20.0999 21.3299 20.0999 20.9999V16.9999V2.9999C20.0999 2.6699 19.8299 2.3999 19.4999 2.3999ZM5.0999 4.9999V16.8118C5.50468 16.5513 5.98546 16.3999 6.4999 16.3999H18.8999V3.5999H6.4999C5.7299 3.5999 5.0999 4.2299 5.0999 4.9999ZM6.4999 17.5999H18.8999V20.3999H6.4999C5.7299 20.3999 5.0999 19.7699 5.0999 18.9999C5.0999 18.2299 5.7299 17.5999 6.4999 17.5999ZM8.4999 8.5999H15.4999C15.8299 8.5999 16.0999 8.3299 16.0999 7.9999C16.0999 7.6699 15.8299 7.3999 15.4999 7.3999H8.4999C8.1699 7.3999 7.8999 7.6699 7.8999 7.9999C7.8999 8.3299 8.1699 8.5999 8.4999 8.5999ZM15.4999 11.3999H8.4999C8.1699 11.3999 7.8999 11.6699 7.8999 11.9999C7.8999 12.3299 8.1699 12.5999 8.4999 12.5999H15.4999C15.8299 12.5999 16.0999 12.3299 16.0999 11.9999C16.0999 11.6699 15.8299 11.3999 15.4999 11.3999Z"/></svg>',
},
],
}
}

View File

@ -26,32 +26,42 @@ const withoutInitialState = ref<boolean | undefined>()
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isChecked">
<input
v-model="isChecked"
type="checkbox"
>
{{ isChecked }}
</Variant>
<Variant title="Enabled Initially">
<FancyCheckbox
:disabled="isDisabled"
v-model="isCheckedInitiallyEnabled"
:disabled="isDisabled"
>
We want you to use this option
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isCheckedInitiallyEnabled">
<input
v-model="isCheckedInitiallyEnabled"
type="checkbox"
>
{{ isCheckedInitiallyEnabled }}
</Variant>
<Variant title="Disabled">
<FancyCheckbox
disabled
:modelValue="isCheckedDisabled"
@update:model-value="logEvent('Setting disabled: This should never happen', $event)"
:model-value="isCheckedDisabled"
@update:modelValue="logEvent('Setting disabled: This should never happen', $event)"
>
You can't change this
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isCheckedDisabled" disabled>
<input
v-model="isCheckedDisabled"
type="checkbox"
disabled
>
{{ isCheckedDisabled }}
</Variant>
@ -64,7 +74,11 @@ const withoutInitialState = ref<boolean | undefined>()
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="withoutInitialState" disabled>
<input
v-model="withoutInitialState"
type="checkbox"
disabled
>
{{ withoutInitialState }}
</Variant>
</Story>

View File

@ -7,11 +7,14 @@
}"
:disabled="disabled"
:model-value="modelValue"
@update:model-value="value => emit('update:modelValue', value)"
@update:modelValue="value => emit('update:modelValue', value)"
>
<CheckboxIcon class="fancycheckbox__icon" />
<span v-if="$slots.default" class="fancycheckbox__content">
<slot/>
<span
v-if="$slots.default"
class="fancycheckbox__content"
>
<slot />
</span>
</BaseCheckbox>
</template>

View File

@ -1,56 +1,87 @@
<template>
<div
ref="multiselectRoot"
class="multiselect"
:class="{'has-search-results': searchResultsVisible}"
ref="multiselectRoot"
tabindex="-1"
@focus="focus"
>
<div class="control" :class="{'is-loading': loading || localLoading}">
<div
class="control"
:class="{'is-loading': loading || localLoading}"
>
<div
class="input-wrapper input"
:class="{'has-multiple': hasMultiple}"
:class="{'has-multiple': hasMultiple, 'has-removal-button': removalAvailable}"
>
<template v-if="Array.isArray(internalValue)">
<slot
v-if="Array.isArray(internalValue)"
name="items"
:items="internalValue"
:remove="remove"
>
<template v-for="(item, key) in internalValue">
<slot name="tag" :item="item">
<span :key="`item${key}`" class="tag ml-2 mt-2">
{{ label !== '' ? item[label] : item }}
<BaseButton @click="() => remove(item)" class="delete is-small"></BaseButton>
</span>
<slot
name="tag"
:item="item"
>
<span
:key="`item${key}`"
class="tag ml-2 mt-2"
>
{{ label !== '' ? item[label] : item }}
<BaseButton
class="delete is-small"
@click="() => remove(item)"
/>
</span>
</slot>
</template>
</template>
</slot>
<input
ref="searchInput"
v-model="query"
type="text"
class="input"
v-model="query"
@keyup="search"
@keyup.enter.exact.prevent="() => createOrSelectOnEnter()"
:placeholder="placeholder"
@keydown.down.exact.prevent="() => preSelect(0)"
ref="searchInput"
@focus="handleFocus"
:autocomplete="autocompleteEnabled ? undefined : 'off'"
:spellcheck="autocompleteEnabled ? undefined : 'false'"
/>
@keyup="search"
@keyup.enter.exact.prevent="() => createOrSelectOnEnter()"
@keydown.down.exact.prevent="() => preSelect(0)"
@focus="handleFocus"
>
<BaseButton
v-if="removalAvailable"
class="removal-button"
@click="resetSelectedValue"
>
<icon icon="times" />
</BaseButton>
</div>
</div>
<CustomTransition name="fade">
<div class="search-results" :class="{'search-results-inline': inline}" v-if="searchResultsVisible">
<div
v-if="searchResultsVisible"
class="search-results"
:class="{'search-results-inline': inline}"
>
<BaseButton
class="search-result-button is-fullwidth"
v-for="(data, index) in filteredSearchResults"
:key="index"
:ref="(el) => setResult(el, index)"
class="search-result-button is-fullwidth"
@keydown.up.prevent="() => preSelect(index - 1)"
@keydown.down.prevent="() => preSelect(index + 1)"
@click.prevent.stop="() => select(data)"
>
<span>
<slot name="searchResult" :option="data">
<slot
name="searchResult"
:option="data"
>
<span class="search-result">{{ label !== '' ? data[label] : data }}</span>
</slot>
</span>
@ -61,15 +92,18 @@
<BaseButton
v-if="creatableAvailable"
class="search-result-button is-fullwidth"
:ref="(el) => setResult(el, filteredSearchResults.length)"
class="search-result-button is-fullwidth"
@keydown.up.prevent="() => preSelect(filteredSearchResults.length - 1)"
@keydown.down.prevent="() => preSelect(filteredSearchResults.length + 1)"
@keyup.enter.prevent="create"
@click.prevent.stop="create"
>
<span>
<slot name="searchResult" :option="query">
<slot
name="searchResult"
:option="query"
>
<span class="search-result">
{{ query }}
</span>
@ -85,7 +119,9 @@
</template>
<script setup lang="ts">
import {computed, onBeforeUnmount, onMounted, ref, toRefs, watch, type ComponentPublicInstance, type PropType} from 'vue'
import {
computed, onBeforeUnmount, onMounted, ref, toRefs, watch, type ComponentPublicInstance, type PropType,
} from 'vue'
import {useI18n} from 'vue-i18n'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
@ -93,15 +129,6 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import BaseButton from '@/components/base/BaseButton.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
function elementInResults(elem: string | any, label: string, query: string): boolean {
// Don't make create available if we have an exact match in our search results.
if (label !== '') {
return elem[label] === query
}
return elem === query
}
const props = defineProps({
/**
* When true, shows a loading spinner
@ -121,7 +148,8 @@ const props = defineProps({
* The search results where the @search listener needs to put the results into
*/
searchResults: {
type: Array as PropType<{[id: string]: any}>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type: Array as PropType<{ [id: string]: any }>,
default: () => [],
},
/**
@ -136,7 +164,8 @@ const props = defineProps({
* The object with the value, updated every time an entry is selected.
*/
modelValue: {
type: [Object] as PropType<{[key: string]: any}>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type: [Object] as PropType<{ [key: string]: any }>,
default: null,
},
/**
@ -152,7 +181,7 @@ const props = defineProps({
createPlaceholder: {
type: String,
default() {
const {t} = useI18n({useScope: 'global'})
const {t} = useI18n({useScope: 'global'})
return t('input.multiselect.createPlaceholder')
},
},
@ -162,7 +191,7 @@ const props = defineProps({
selectPlaceholder: {
type: String,
default() {
const {t} = useI18n({useScope: 'global'})
const {t} = useI18n({useScope: 'global'})
return t('input.multiselect.selectPlaceholder')
},
},
@ -208,30 +237,43 @@ const props = defineProps({
})
const emit = defineEmits<{
(e: 'update:modelValue', value: null): void
(e: 'update:modelValue', value: null): void
/**
* Triggered every time the search query input changes
*/
(e: 'search', query: string): void
(e: 'search', query: string): void
/**
* Triggered every time an option from the search results is selected. Also triggers a change in v-model.
*/
(e: 'select', value: {[key: string]: any}): void
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(e: 'select', value: { [key: string]: any }): void
/**
* If nothing or no exact match was found and `creatable` is true, this event is triggered with the current value of the search query.
*/
(e: 'create', query: string): void
(e: 'create', query: string): void
/**
* If `multiple` is enabled, this will be fired every time an item is removed from the array of selected items.
*/
(e: 'remove', value: null): void
(e: 'remove', value: null): void
}>()
const query = ref<string | {[key: string]: any}>('')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function elementInResults(elem: string | any, label: string, query: string): boolean {
// Don't make create available if we have an exact match in our search results.
if (label !== '') {
return elem[label] === query
}
return elem === query
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const query = ref<string | { [key: string]: any }>('')
const searchTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
const localLoading = ref(false)
const showSearchResults = ref(false)
const internalValue = ref<string | {[key: string]: any} | any[] | null>(null)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const internalValue = ref<string | { [key: string]: any } | any[] | null>(null)
onMounted(() => document.addEventListener('click', hideSearchResultsHandler))
onBeforeUnmount(() => document.removeEventListener('click', hideSearchResultsHandler))
@ -259,17 +301,19 @@ const searchResultsVisible = computed(() => {
})
const creatableAvailable = computed(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const hasResult = filteredSearchResults.value.some((elem: any) => elementInResults(elem, props.label, query.value))
const hasQueryAlreadyAdded = Array.isArray(internalValue.value) && internalValue.value.some(elem => elementInResults(elem, props.label, query.value))
return props.creatable
&& query.value !== ''
return props.creatable
&& query.value !== ''
&& !(hasResult || hasQueryAlreadyAdded)
})
const filteredSearchResults = computed(() => {
const currentInternal = internalValue.value
if (props.multiple && currentInternal !== null && Array.isArray(currentInternal)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return searchResults.value.filter((item: any) => !currentInternal.some(e => e === item))
}
@ -280,7 +324,13 @@ const hasMultiple = computed(() => {
return props.multiple && Array.isArray(internalValue.value) && internalValue.value.length > 0
})
const removalAvailable = computed(() => !props.multiple && internalValue.value !== null && query.value !== '')
function resetSelectedValue() {
select(null)
}
const searchInput = ref<HTMLInputElement | null>(null)
// Searching will be triggered with a 200ms delay to avoid searching on every keyup event.
function search() {
@ -305,6 +355,7 @@ function search() {
}
const multiselectRoot = ref<HTMLElement | null>(null)
function hideSearchResultsHandler(e: MouseEvent) {
closeWhenClickedOutside(e, multiselectRoot.value, closeSearchResults)
}
@ -321,12 +372,14 @@ function handleFocus() {
}, 10)
}
function select(object: {[key: string]: any}) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function select(object: { [key: string]: any } | null) {
if (props.multiple) {
if (internalValue.value === null) {
internalValue.value = []
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(internalValue.value as any[]).push(object)
} else {
internalValue.value = object
@ -340,7 +393,8 @@ function select(object: {[key: string]: any}) {
}
}
function setSelectedObject(object: string | {[id: string]: any} | null, resetOnly = false) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setSelectedObject(object: string | { [id: string]: any } | null, resetOnly = false) {
internalValue.value = object
// We assume we're getting an array when multiple is enabled and can therefore leave the query
@ -363,6 +417,7 @@ function setSelectedObject(object: string | {[id: string]: any} | null, resetOnl
}
const results = ref<(Element | ComponentPublicInstance)[]>([])
function setResult(el: Element | ComponentPublicInstance | null, index: number) {
if (el === null) {
delete results.value[index]
@ -408,8 +463,9 @@ function createOrSelectOnEnter() {
if (!creatableAvailable.value) {
// Check if there's an exact match for our search term
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const exactMatch = filteredSearchResults.value.find((elem: any) => elementInResults(elem, props.label, query.value))
if(exactMatch) {
if (exactMatch) {
select(exactMatch)
}
@ -419,6 +475,7 @@ function createOrSelectOnEnter() {
create()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function remove(item: any) {
for (const ind in internalValue.value) {
if (internalValue.value[ind] === item) {
@ -565,4 +622,14 @@ function focus() {
transition: color $transition;
padding-left: .5rem;
}
.has-removal-button {
position: relative;
}
.removal-button {
position: absolute;
right: .5rem;
color: var(--danger);
}
</style>

View File

@ -1,27 +1,31 @@
<template>
<div class="password-field">
<input
class="input"
id="password"
class="input"
name="password"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
:type="passwordFieldType"
autocomplete="current-password"
@keyup.enter="e => $emit('submit', e)"
:tabindex="props.tabindex"
@keyup.enter="e => $emit('submit', e)"
@focusout="validate"
@input="handleInput"
/>
>
<BaseButton
@click="togglePasswordFieldType"
v-tooltip="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')"
class="password-field-type-toggle"
:aria-label="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')"
v-tooltip="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')">
<icon :icon="passwordFieldType === 'password' ? 'eye' : 'eye-slash'"/>
@click="togglePasswordFieldType"
>
<icon :icon="passwordFieldType === 'password' ? 'eye' : 'eye-slash'" />
</BaseButton>
</div>
<p class="help is-danger" v-if="!isValid">
<p
v-if="!isValid"
class="help is-danger"
>
{{ $t('user.auth.passwordRequired') }}
</p>
</template>

View File

@ -1,522 +0,0 @@
<template>
<div class="vue-easymde" ref="easymdeRef">
<textarea
class="vue-simplemde-textarea"
:name="name"
:value="modelValue"
@input="handleInput(($event.target as HTMLTextAreaElement).value)"
/>
</div>
</template>
<script setup lang="ts">
import {ref, watch, onMounted, onDeactivated, onBeforeUnmount, nextTick, shallowReactive, type ShallowReactive, type PropType} from 'vue'
import EasyMDE, {toggleFullScreen} from 'easymde'
import {marked} from 'marked'
import type CodeMirror from 'codemirror'
const props = defineProps({
modelValue: {
type: String,
default: '',
},
name: {
type: String,
},
previewClass: {
type: String,
},
autoinit: {
type: Boolean,
default: true,
},
highlight: {
type: Boolean,
default: false,
},
sanitize: {
type: Boolean,
default: false,
},
configs: {
type: Object,
default: () => ({}),
},
previewRender: {
type: Function as PropType<EasyMDE.Options['previewRender']>,
},
})
const emit = defineEmits(['update:modelValue', 'blur', 'initialized'])
const isValueUpdateFromInner = ref(false)
let easymde: ShallowReactive<EasyMDE> | undefined
onMounted(() => {
if (props.autoinit) initialize()
})
onDeactivated(() => {
if (easymde === undefined) return
if (easymde.isFullscreenActive()) toggleFullScreen(easymde)
easymde.toTextArea
})
onBeforeUnmount(() => {
if (easymde) {
easymde.toTextArea()
easymde.cleanup()
easymde = undefined
}
})
const easymdeRef = ref<HTMLElement | null>(null)
function initialize() {
const configs: EasyMDE.Options = Object.assign({
element: easymdeRef.value?.firstElementChild as HTMLElement,
initialValue: props.modelValue,
previewRender: props.previewRender,
renderingConfig: {},
}, props.configs)
// Synchronize the values of value and initialValue
if (configs.initialValue) {
emit('update:modelValue', configs.initialValue)
}
// Determine whether to enable code highlighting
if (props.highlight) {
configs.renderingConfig!.codeSyntaxHighlighting = true
}
// Set whether to render the input html
marked.setOptions({ sanitize: props.sanitize })
// Instantiated editor
easymde = shallowReactive(new EasyMDE(configs))
// Add a custom previewClass
const className = props.previewClass || ''
addPreviewClass(easymde, className)
// Binding event
easymde.codemirror.on('change', handleCodemirrorInput)
easymde.codemirror.on('blur', handleCodemirrorBlur)
nextTick(() => emit('initialized', easymde))
}
function addPreviewClass(easymde: EasyMDE, className: string) {
const wrapper = easymde.codemirror.getWrapperElement()
const preview = document.createElement('div')
wrapper.nextSibling.className += ` ${className}`
preview.className = `editor-preview ${className}`
wrapper.appendChild(preview)
}
function handleInput(val: string) {
isValueUpdateFromInner.value = true
emit('update:modelValue', val)
}
function handleCodemirrorInput(instance: CodeMirror.Editor, changeObj: CodeMirror.EditorChange) {
if (changeObj.origin === 'setValue' || easymde === undefined) {
return
}
handleInput(easymde.value())
}
function handleCodemirrorBlur() {
if (easymde === undefined) {
return
}
isValueUpdateFromInner.value = true
emit('blur', easymde.value())
}
watch(
() => props.modelValue,
(val) => {
if (isValueUpdateFromInner.value) {
isValueUpdateFromInner.value = false
} else {
easymde?.value(val)
}
},
)
</script>
<style lang="scss">
.EasyMDEContainer {
display: block;
}
.EasyMDEContainer.sided--no-fullscreen {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.EasyMDEContainer .CodeMirror {
box-sizing: border-box;
height: auto;
border: 1px solid #ddd;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
padding: 10px;
font: inherit;
z-index: 0;
word-wrap: break-word;
}
.EasyMDEContainer .CodeMirror-scroll {
cursor: text;
}
.EasyMDEContainer .CodeMirror-fullscreen {
background: #fff;
position: fixed !important;
top: 50px;
left: 0;
right: 0;
bottom: 0;
height: auto;
z-index: 8;
border-right: none !important;
border-bottom-right-radius: 0 !important;
}
.EasyMDEContainer .CodeMirror-sided {
width: 50% !important;
}
.EasyMDEContainer.sided--no-fullscreen .CodeMirror-sided {
border-right: none!important;
border-bottom-right-radius: 0px;
position: relative;
flex: 1 1 auto;
}
.EasyMDEContainer .CodeMirror-placeholder {
opacity: .5;
}
.EasyMDEContainer .CodeMirror-focused .CodeMirror-selected {
background: #d9d9d9;
}
.editor-toolbar {
position: relative;
user-select: none;
padding: 9px 10px;
border-top: 1px solid #bbb;
border-left: 1px solid #bbb;
border-right: 1px solid #bbb;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.editor-toolbar.fullscreen {
width: 100%;
height: 50px;
padding-top: 10px;
padding-bottom: 10px;
box-sizing: border-box;
background: #fff;
border: 0;
position: fixed;
top: 0;
left: 0;
opacity: 1;
z-index: 9;
}
.editor-toolbar.fullscreen::before {
width: 20px;
height: 50px;
background: linear-gradient(to right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
position: fixed;
top: 0;
left: 0;
margin: 0;
padding: 0;
}
.editor-toolbar.fullscreen::after {
width: 20px;
height: 50px;
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
position: fixed;
top: 0;
right: 0;
margin: 0;
padding: 0;
}
.EasyMDEContainer.sided--no-fullscreen .editor-toolbar {
width: 100%;
}
.editor-toolbar button, .editor-toolbar .easymde-dropdown {
background: transparent;
display: inline-block;
text-align: center;
text-decoration: none !important;
height: 30px;
margin: 0;
padding: 0;
border: 1px solid transparent;
border-radius: 3px;
cursor: pointer;
}
.editor-toolbar button {
width: 30px;
}
.editor-toolbar button.active,
.editor-toolbar button:hover {
background: #fcfcfc;
border-color: #95a5a6;
}
.editor-toolbar i.separator {
display: inline-block;
width: 0;
border-left: 1px solid #d9d9d9;
border-right: 1px solid #fff;
color: transparent;
text-indent: -10px;
margin: 0 6px;
}
.editor-toolbar button:after {
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
font-size: 65%;
vertical-align: text-bottom;
position: relative;
top: 2px;
}
.editor-toolbar button.heading-1:after {
content: "1";
}
.editor-toolbar button.heading-2:after {
content: "2";
}
.editor-toolbar button.heading-3:after {
content: "3";
}
.editor-toolbar button.heading-bigger:after {
content: "▲";
}
.editor-toolbar button.heading-smaller:after {
content: "▼";
}
.editor-toolbar.disabled-for-preview button:not(.no-disable) {
opacity: .6;
pointer-events: none;
}
@media only screen and (max-width: 700px) {
.editor-toolbar i.no-mobile {
display: none;
}
}
.editor-statusbar {
padding: 8px 10px;
font-size: 12px;
color: #959694;
text-align: right;
}
.EasyMDEContainer.sided--no-fullscreen .editor-statusbar {
width: 100%;
}
.editor-statusbar span {
display: inline-block;
min-width: 4em;
margin-left: 1em;
}
.editor-statusbar .lines:before {
content: 'lines: '
}
.editor-statusbar .words:before {
content: 'words: '
}
.editor-statusbar .characters:before {
content: 'characters: '
}
.editor-preview-full {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 7;
overflow: auto;
display: none;
box-sizing: border-box;
}
.editor-preview-side {
position: fixed;
bottom: 0;
width: 50%;
top: 50px;
right: 0;
z-index: 9;
overflow: auto;
display: none;
box-sizing: border-box;
border: 1px solid #ddd;
word-wrap: break-word;
}
.editor-preview-active-side {
display: block
}
.EasyMDEContainer.sided--no-fullscreen .editor-preview-active-side {
flex: 1 1 auto;
height: auto;
position: static;
}
.editor-preview-active {
display: block
}
.editor-preview {
padding: 10px;
background: #fafafa;
}
.editor-preview > p {
margin-top: 0
}
.editor-preview pre {
background: #eee;
margin-bottom: 10px;
}
.editor-preview table td,
.editor-preview table th {
border: 1px solid #ddd;
padding: 5px;
}
.cm-s-easymde .cm-tag {
color: #63a35c;
}
.cm-s-easymde .cm-attribute {
color: #795da3;
}
.cm-s-easymde .cm-string {
color: #183691;
}
.cm-s-easymde .cm-header-1 {
font-size: 200%;
line-height: 200%;
}
.cm-s-easymde .cm-header-2 {
font-size: 160%;
line-height: 160%;
}
.cm-s-easymde .cm-header-3 {
font-size: 125%;
line-height: 125%;
}
.cm-s-easymde .cm-header-4 {
font-size: 110%;
line-height: 110%;
}
.cm-s-easymde .cm-comment {
background: rgba(0, 0, 0, .05);
border-radius: 2px;
}
.cm-s-easymde .cm-link {
color: #7f8c8d;
}
.cm-s-easymde .cm-url {
color: #aab2b3;
}
.cm-s-easymde .cm-quote {
color: #7f8c8d;
font-style: italic;
}
.editor-toolbar .easymde-dropdown {
position: relative;
background: linear-gradient(to bottom right, #fff 0%, #fff 84%, #333 50%, #333 100%);
border-radius: 0;
border: 1px solid #fff;
}
.editor-toolbar .easymde-dropdown:hover {
background: linear-gradient(to bottom right, #fff 0%, #fff 84%, #333 50%, #333 100%);
}
.easymde-dropdown-content {
display: block;
visibility: hidden;
position: absolute;
background-color: #f9f9f9;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
padding: 8px;
z-index: 2;
top: 30px;
}
.easymde-dropdown:active .easymde-dropdown-content,
.easymde-dropdown:focus .easymde-dropdown-content {
visibility: visible;
}
span[data-img-src]::after{
content: '';
background-image: var(--bg-image);
display: block;
max-height: 100%;
max-width: 100%;
background-size: contain;
height: 0;
padding-top: var(--height);
width: var(--width);
background-repeat: no-repeat;
}
</style>
<style lang="scss" scoped>
.vue-easymde .markdown-body {
padding: 0.5em
}
.vue-easymde .editor-preview-active,
.vue-easymde .editor-preview-active-side {
display: block;
}
</style>

View File

@ -1,5 +1,7 @@
<template>
<BaseButton class="button-link"><slot/></BaseButton>
<BaseButton class="button-link">
<slot />
</BaseButton>
</template>
<script setup lang="ts">

View File

@ -1,6 +1,7 @@
<template>
<transition :name="name">
<slot />
<!-- eslint-disable-next-line -->
<slot/>
</transition>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div
<div
v-if="isDone"
class="is-done"
:class="{ 'is-done--small': variant === 'small' }"

View File

@ -2,11 +2,17 @@ import {library} from '@fortawesome/fontawesome-svg-core'
import {
faAlignLeft,
faAngleRight,
faAnglesUp,
faArchive,
faArrowLeft,
faArrowUpFromBracket,
faBold,
faItalic,
faStrikethrough,
faCode,
faBars,
faBell,
faBolt,
faCalendar,
faCheck,
faCheckDouble,
@ -27,6 +33,7 @@ import {
faFilter,
faForward,
faGripLines,
faHeader,
faHistory,
faImage,
faKeyboard,
@ -57,14 +64,26 @@ import {
faTimes,
faTrashAlt,
faUser,
faUsers, faX,
faUsers,
faQuoteRight,
faListUl,
faLink,
faUndo,
faRedo,
faUnlink,
faParagraph,
faTable,
faX, faArrowTurnDown, faListCheck, faXmark, faXmarksLines, faFont, faRulerHorizontal, faUnderline,
} from '@fortawesome/free-solid-svg-icons'
import {
faBellSlash,
faCalendarAlt,
faCheckSquare,
faClock,
faComments,
faFileImage,
faSave,
faSquareCheck,
faStar,
faSun,
faTimesCircle,
@ -74,6 +93,21 @@ import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
import type {FontAwesomeIcon as FontAwesomeIconFixedTypes} from '@/types/vue-fontawesome'
library.add(faBold)
library.add(faUndo)
library.add(faRedo)
library.add(faItalic)
library.add(faLink)
library.add(faUnlink)
library.add(faParagraph)
library.add(faSquareCheck)
library.add(faTable)
library.add(faFileImage)
library.add(faCheckSquare)
library.add(faStrikethrough)
library.add(faCode)
library.add(faQuoteRight)
library.add(faListUl)
library.add(faAlignLeft)
library.add(faAngleRight)
library.add(faArchive)
@ -105,6 +139,7 @@ library.add(faFillDrip)
library.add(faFilter)
library.add(faForward)
library.add(faGripLines)
library.add(faHeader)
library.add(faHistory)
library.add(faImage)
library.add(faKeyboard)
@ -142,6 +177,15 @@ library.add(faUser)
library.add(faUsers)
library.add(faArrowUpFromBracket)
library.add(faX)
library.add(faAnglesUp)
library.add(faBolt)
library.add(faArrowTurnDown)
library.add(faListCheck)
library.add(faXmark)
library.add(faXmarksLines)
library.add(faFont)
library.add(faRulerHorizontal)
library.add(faUnderline)
// overwriting the wrong types
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import BaseButton from '@/components/base/BaseButton.vue'
import {useBaseStore} from '@/stores/base'
import {onBeforeUnmount, onMounted} from 'vue'
import {eventToHotkeyString} from '@github/hotkey'
const baseStore = useBaseStore()
// See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660
function openQuickActionsViaHotkey(event) {
const hotkeyString = eventToHotkeyString(event)
if (!hotkeyString) return
if (hotkeyString !== 'Control+k' && hotkeyString !== 'Meta+k') return
event.preventDefault()
openQuickActions()
}
onMounted(() => {
document.addEventListener('keydown', openQuickActionsViaHotkey)
})
onBeforeUnmount(() => {
document.removeEventListener('keydown', openQuickActionsViaHotkey)
})
function openQuickActions() {
baseStore.setQuickActionsActive(true)
}
</script>
<template>
<BaseButton
class="trigger-button"
:title="$t('keyboardShortcuts.quickSearch')"
@click="openQuickActions"
>
<icon icon="search" />
</BaseButton>
</template>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import {ref} from 'vue'
import ProgressBar from './ProgressBar.vue'
const value = ref(50)
</script>
<template>
<Story>
<Variant title="Default">
<ProgressBar :value="value" />
</Variant>
</Story>
</template>

View File

@ -0,0 +1,139 @@
<template>
<progress
class="progress-bar"
:class="{
'is-small': isSmall,
'is-primary': isPrimary,
}"
:value="value"
max="100"
>
{{ value }}%
</progress>
</template>
<script setup lang="ts">
import {defineProps} from 'vue'
defineProps({
value: {
type: Number,
required: true,
},
isSmall: {
type: Boolean,
default: false,
},
isPrimary: {
type: Boolean,
required: false,
},
})
</script>
<style lang="scss" scoped>
.progress-bar {
--progress-height: #{$size-normal};
--progress-bar-background-color: var(--border-light, #{$border-light});
--progress-value-background-color: var(--grey-500, #{$text});
--progress-border-radius: #{$radius};
--progress-indeterminate-duration: 1.5s;
appearance: none;
border: none;
border-radius: var(--progress-border-radius);
height: var(--progress-height);
overflow: hidden;
padding: 0;
min-width: 6vw;
width: 50px;
margin: 0 .5rem 0 0;
flex: 3 1 auto;
&::-moz-progress-bar,
&::-webkit-progress-value {
background: var(--progress-value-background-color);
}
@media screen and (max-width: $tablet) {
margin: 0.5rem 0 0 0;
order: 1;
width: 100%;
}
&::-webkit-progress-bar {
background-color: var(--progress-bar-background-color);
}
&::-webkit-progress-value {
background-color: var(--progress-value-background-color);
}
&::-moz-progress-bar {
background-color: var(--progress-value-background-color);
}
&::-ms-fill {
background-color: var(--progress-value-background-color);
border: none;
}
// Colors
@each $name, $pair in $colors {
$color: nth($pair, 1);
&.is-#{$name} {
--progress-value-background-color: var(--#{$name}, #{$color});
&:indeterminate {
background-image: linear-gradient(
to right,
var(--#{$name}, #{$color}) 30%,
var(--progress-bar-background-color) 30%
);
}
}
}
&:indeterminate {
animation-duration: var(--progress-indeterminate-duration);
animation-iteration-count: infinite;
animation-name: moveIndeterminate;
animation-timing-function: linear;
background-color: var(--progress-bar-background-color);
background-image: linear-gradient(
to right,
var(--text, #{$text}) 30%,
var(--progress-bar-background-color) 30%
);
background-position: top left;
background-repeat: no-repeat;
background-size: 150% 150%;
&::-webkit-progress-bar {
background-color: transparent;
}
&::-moz-progress-bar {
background-color: transparent;
}
&::-ms-fill {
animation-name: none;
}
}
&.is-small {
--progress-height: #{$size-small};
}
}
@keyframes moveIndeterminate {
from {
background-position: 200% 0;
}
to {
background-position: -200% 0;
}
}
</style>

View File

@ -1,38 +1,62 @@
<template>
<div class="api-config">
<div v-if="configureApi">
<label class="label" for="api-url">{{ $t('apiConfig.url') }}</label>
<label
class="label"
for="api-url"
>{{ $t('apiConfig.url') }}</label>
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input"
id="api-url"
v-model="apiUrl"
v-focus
class="input"
:placeholder="$t('apiConfig.urlPlaceholder')"
required
type="url"
v-focus
v-model="apiUrl"
@keyup.enter="setApiUrl"
/>
>
</div>
<div class="control">
<x-button @click="setApiUrl" :disabled="apiUrl === '' || undefined">
<x-button
:disabled="apiUrl === '' || undefined"
@click="setApiUrl"
>
{{ $t('apiConfig.change') }}
</x-button>
</div>
</div>
</div>
<div class="api-url-info" v-else>
<i18n-t keypath="apiConfig.use" scope="global">
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
<div
v-else
class="api-url-info"
>
<i18n-t
keypath="apiConfig.use"
scope="global"
>
<span
v-tooltip="apiUrl"
class="url"
> {{ apiDomain }} </span>
</i18n-t>
<br/>
<ButtonLink class="api-config__change-button" @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</ButtonLink>
<br>
<ButtonLink
class="api-config__change-button"
@click="() => (configureApi = true)"
>
{{ $t('apiConfig.change') }}
</ButtonLink>
</div>
<message variant="danger" v-if="errorMsg !== ''" class="mt-2">
<Message
v-if="errorMsg !== ''"
variant="danger"
class="mt-2"
>
{{ errorMsg }}
</message>
</Message>
</div>
</template>
@ -57,7 +81,7 @@ const props = defineProps({
const emit = defineEmits(['foundApi'])
const apiUrl = ref(window.API_URL)
const configureApi = ref(apiUrl.value === '')
const configureApi = ref(window.API_URL === '')
// Because we're only using this to parse the hostname, it should be fine to just prefix with http://
// regardless of whether the url is actually reachable under http.

View File

@ -1,18 +1,24 @@
<template>
<div class="card" :class="{'has-no-shadow': !shadow}">
<header class="card-header" v-if="title !== ''">
<div
class="card"
:class="{'has-no-shadow': !shadow}"
>
<header
v-if="title !== ''"
class="card-header"
>
<p class="card-header-title">
{{ title }}
</p>
<BaseButton
v-if="hasClose"
v-tooltip="$t('misc.close')"
class="card-header-icon"
:aria-label="$t('misc.close')"
@click="$emit('close')"
v-tooltip="$t('misc.close')"
>
<span class="icon">
<icon :icon="closeIcon"/>
<icon :icon="closeIcon" />
</span>
</BaseButton>
</header>
@ -24,12 +30,15 @@
}"
>
<div :class="{'content': hasContent}">
<slot/>
<slot />
</div>
</div>
<footer v-if="$slots.footer" class="card-footer">
<slot name="footer"/>
<footer
v-if="$slots.footer"
class="card-footer"
>
<slot name="footer" />
</footer>
</div>
</template>

View File

@ -2,7 +2,7 @@
<span
:style="{backgroundColor: color }"
class="color-bubble"
></span>
/>
</template>
<script lang="ts" setup>

View File

@ -1,16 +1,20 @@
<template>
<modal @close="$router.back()" :overflow="true" :wide="wide">
<modal
:overflow="true"
:wide="wide"
@close="$router.back()"
>
<card
:title="title"
:shadow="false"
:padding="false"
class="has-text-left"
:has-close="true"
@close="$router.back()"
:loading="loading"
@close="$router.back()"
>
<div class="p-4">
<slot/>
<slot />
</div>
<template #footer>
@ -32,10 +36,10 @@
<x-button
v-if="hasPrimaryAction"
variant="primary"
@click.prevent.stop="primary()"
:icon="primaryIcon"
:disabled="primaryDisabled || loading"
class="ml-2"
@click.prevent.stop="primary()"
>
{{ primaryLabel || $t('misc.create') }}
</x-button>

View File

@ -1,21 +1,26 @@
<template>
<BaseButton class="dropdown-item">
<span class="icon" v-if="icon">
<Icon :icon="icon"/>
<span
v-if="icon"
class="icon is-small"
:class="iconClass"
>
<Icon :icon="icon" />
</span>
<span>
<slot />
<slot />
</span>
</BaseButton>
</template>
<script lang="ts" setup>
import BaseButton, { type BaseButtonProps } from '@/components/base//BaseButton.vue'
import BaseButton, {type BaseButtonProps} from '@/components/base//BaseButton.vue'
import Icon from '@/components/misc/Icon'
import type { IconProp } from '@fortawesome/fontawesome-svg-core'
import type {IconProp} from '@fortawesome/fontawesome-svg-core'
export interface DropDownItemProps extends /* @vue-ignore */ BaseButtonProps {
icon?: IconProp,
iconClass?: object | string,
}
defineProps<DropDownItemProps>()
@ -24,7 +29,6 @@ defineProps<DropDownItemProps>()
<style scoped lang="scss">
.dropdown-item {
color: var(--text);
display: block;
font-size: 0.875rem;
line-height: 1.5;
padding: $item-padding;
@ -52,10 +56,7 @@ defineProps<DropDownItemProps>()
.icon {
padding-right: .5rem;
&:not(.has-text-success) {
color: var(--grey-300) !important;
}
color: var(--grey-300);
}
.has-text-danger .icon {

View File

@ -1,15 +1,32 @@
<template>
<div class="dropdown" ref="dropdown">
<slot name="trigger" :close="close" :toggleOpen="toggleOpen" :open="open">
<BaseButton class="dropdown-trigger is-flex" @click="toggleOpen">
<icon :icon="triggerIcon" class="icon"/>
<div
ref="dropdown"
class="dropdown"
>
<slot
name="trigger"
:close="close"
:toggle-open="toggleOpen"
:open="open"
>
<BaseButton
class="dropdown-trigger is-flex"
@click="toggleOpen"
>
<icon
:icon="triggerIcon"
class="icon"
/>
</BaseButton>
</slot>
<CustomTransition name="fade">
<div class="dropdown-menu" v-if="open">
<div
v-if="open"
class="dropdown-menu"
>
<div class="dropdown-content">
<slot :close="close"></slot>
<slot :close="close" />
</div>
</div>
</CustomTransition>

View File

@ -1,10 +1,17 @@
<template>
<message variant="danger">
<i18n-t keypath="loadingError.failed" scope="global">
<ButtonLink @click="reload">{{ $t('loadingError.tryAgain') }}</ButtonLink>
<ButtonLink href="https://vikunja.io/contact/">{{ $t('loadingError.contact') }}</ButtonLink>
<Message variant="danger">
<i18n-t
keypath="loadingError.failed"
scope="global"
>
<ButtonLink @click="reload">
{{ $t('loadingError.tryAgain') }}
</ButtonLink>
<ButtonLink href="https://vikunja.io/contact/">
{{ $t('loadingError.contact') }}
</ButtonLink>
</i18n-t>
</message>
</Message>
</template>
<script lang="ts">

View File

@ -1,11 +1,11 @@
<template>
<input
type="text"
data-input
:disabled="disabled"
v-bind="attrs"
ref="root"
/>
ref="root"
type="text"
data-input
:disabled="disabled"
>
</template>
<script lang="ts">
@ -20,39 +20,39 @@ type Options = flatpickr.Options.Options
type DateOption = flatpickr.Options.DateOption
function camelToKebab(string: string) {
return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}
function arrayify<T = unknown>(obj: T) {
return obj instanceof Array
return obj instanceof Array
? obj
: [obj]
}
function nullify<T = unknown>(value: T) {
return (value && (value as unknown[]).length)
return (value && (value as unknown[]).length)
? value
: null
}
// Events to emit, copied from flatpickr source
const includedEvents = [
'onChange',
'onClose',
'onDestroy',
'onMonthChange',
'onOpen',
'onYearChange',
'onChange',
'onClose',
'onDestroy',
'onMonthChange',
'onOpen',
'onYearChange',
] as HookKey[]
// Let's not emit these events by default
const excludedEvents = [
'onValueUpdate',
'onDayCreate',
'onParseConfig',
'onReady',
'onPreCalendarPosition',
'onKeyDown',
'onValueUpdate',
'onDayCreate',
'onParseConfig',
'onReady',
'onPreCalendarPosition',
'onKeyDown',
] as HookKey[]
// Keep a copy of all events for later use
@ -100,19 +100,19 @@ const attrs = useAttrs()
const root = ref<HTMLInputElement | null>(null)
const fp = ref<flatpickr.Instance | null>(null)
const safeConfig = ref<Options>({ ...props.config })
const safeConfig = ref<Options>({...props.config})
function prepareConfig() {
// Don't mutate original object on parent component
const newConfig: Options = { ...props.config }
const newConfig: Options = {...props.config}
props.events.forEach((hook) => {
// Respect global callbacks registered via setDefault() method
const globalCallbacks = flatpickr.defaultConfig[hook] || []
// Inject our own method along with user callback
const localCallback: Hook = (...args) => emit(camelToKebab(hook), ...args)
// Overwrite with merged array
newConfig[hook] = arrayify(newConfig[hook] || []).concat(
globalCallbacks,
@ -147,9 +147,9 @@ onMounted(() => {
prepareConfig()
/**
* Get the HTML node where flatpickr to be attached
* Bind on parent element if wrap is true
*/
* Get the HTML node where flatpickr to be attached
* Bind on parent element if wrap is true
*/
const element = props.config.wrap
? root.value.parentNode
: root.value
@ -179,7 +179,7 @@ watch(config, () => {
fp.value.set(name, safeConfig.value[name])
}
})
}, {deep:true})
}, {deep: true})
const fpInput = computed(() => {
if (!fp.value) return
@ -198,8 +198,8 @@ watchEffect(() => fpInput.value?.addEventListener('blur', onBlur))
onBeforeUnmount(() => fpInput.value?.removeEventListener('blur', onBlur))
/**
* Watch for the disabled property and sets the value to the real input.
*/
* Watch for the disabled property and sets the value to the real input.
*/
watchEffect(() => {
if (disabled.value) {
fpInput.value?.setAttribute('disabled', '')

View File

@ -1,10 +1,20 @@
<template>
<modal @close="close()">
<card class="has-background-white keyboard-shortcuts" :shadow="false" :title="$t('keyboardShortcuts.title')">
<template v-for="(s, i) in shortcuts" :key="i">
<card
class="has-background-white keyboard-shortcuts"
:shadow="false"
:title="$t('keyboardShortcuts.title')"
>
<template
v-for="(s, i) in shortcuts"
:key="i"
>
<h3>{{ $t(s.title) }}</h3>
<message class="mb-4" v-if="s.available">
<Message
v-if="s.available"
class="mb-4"
>
{{
typeof s.available === 'undefined' ?
$t('keyboardShortcuts.allPages') :
@ -14,14 +24,19 @@
: $t('keyboardShortcuts.somePagesOnly')
)
}}
</message>
</Message>
<dl class="shortcut-list">
<template v-for="(sc, si) in s.shortcuts" :key="si">
<dt class="shortcut-title">{{ $t(sc.title) }}</dt>
<shortcut
class="shortcut-keys"
<template
v-for="(sc, si) in s.shortcuts"
:key="si"
>
<dt class="shortcut-title">
{{ $t(sc.title) }}
</dt>
<Shortcut
is="dd"
class="shortcut-keys"
:keys="sc.keys"
:combination="sc.combination && $t(`keyboardShortcuts.${sc.combination}`)"
/>

View File

@ -152,6 +152,10 @@ export const KEYBOARD_SHORTCUTS : ShortcutGroup[] = [
title: 'keyboardShortcuts.task.favorite',
keys: ['s'],
},
{
title: 'keyboardShortcuts.task.save',
keys: [ctrl, 's'],
},
],
},
]

View File

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

View File

@ -1,5 +1,8 @@
<template>
<div class="loader-container is-loading" :class="{'is-small': variant === 'small'}"></div>
<div
class="loader-container is-loading"
:class="{'is-small': variant === 'small'}"
/>
</template>
<script lang="ts">

View File

@ -1,7 +1,10 @@
<template>
<div class="message-wrapper">
<div class="message" :class="[variant, textAlignClass]">
<slot/>
<div
class="message"
:class="[variant, textAlignClass]"
>
<slot />
</div>
</div>
</template>
@ -9,14 +12,6 @@
<script lang="ts" setup>
import {computed, type PropType} from 'vue'
const TEXT_ALIGN_MAP = Object.freeze({
left: '',
center: 'has-text-centered',
right: 'has-text-right',
})
type textAlignVariants = keyof typeof TEXT_ALIGN_MAP
const props = defineProps({
variant: {
type: String,
@ -28,6 +23,14 @@ const props = defineProps({
},
})
const TEXT_ALIGN_MAP = Object.freeze({
left: '',
center: 'has-text-centered',
right: 'has-text-right',
})
type textAlignVariants = keyof typeof TEXT_ALIGN_MAP
const textAlignClass = computed(() => TEXT_ALIGN_MAP[props.textAlign])
</script>

View File

@ -1,56 +1,59 @@
<template>
<Teleport to="body">
<!-- FIXME: transition should not be included in the modal -->
<CustomTransition :name="transitionName" appear>
<CustomTransition
:name="transitionName"
appear
>
<section
v-if="enabled"
ref="modal"
class="modal-mask"
:class="[
{ 'has-overflow': overflow },
variant,
]"
ref="modal"
v-bind="attrs"
>
<div
class="modal-container"
@click.self.prevent.stop="$emit('close')"
v-shortcut="'Escape'"
class="modal-container"
@mousedown.self.prevent.stop="$emit('close')"
>
<div
class="modal-content"
:class="{
'has-overflow': overflow,
'is-wide': wide
}"
'has-overflow': overflow,
'is-wide': wide
}"
>
<BaseButton
@click="$emit('close')"
class="close"
@click="$emit('close')"
>
<icon icon="times"/>
<icon icon="times" />
</BaseButton>
<slot>
<div class="header">
<slot name="header"></slot>
<slot name="header" />
</div>
<div class="content">
<slot name="text"></slot>
<slot name="text" />
</div>
<div class="actions">
<x-button
@click="$emit('close')"
variant="tertiary"
class="has-text-danger"
@click="$emit('close')"
>
{{ $t('misc.cancel') }}
</x-button>
<x-button
@click="$emit('submit')"
variant="primary"
v-cy="'modalPrimary'"
variant="primary"
:shadow="false"
@click="$emit('submit')"
>
{{ $t('misc.doit') }}
</x-button>
@ -195,10 +198,9 @@ $modal-width: 1024px;
}
.close {
$close-button-min-space: 84px;
$close-button-padding: 26px;
position: fixed;
top: 5px;
top: .5rem;
right: $close-button-padding;
color: var(--grey-900);
font-size: 2rem;
@ -213,6 +215,10 @@ $modal-width: 1024px;
@media screen and (min-width: calc(#{$desktop } + #{$close-button-min-space})) {
color: var(--white);
}
@media screen and (min-width: $tablet) and (max-width: #{$desktop + $close-button-min-space}) {
top: .75rem;
}
}
</style>

View File

@ -1,8 +1,15 @@
<template>
<div class="no-auth-wrapper">
<Logo class="logo" width="200" height="58"/>
<Logo
class="logo"
width="200"
height="58"
/>
<div class="noauth-container">
<section class="image" :class="{'has-message': motd !== ''}">
<section
class="image"
:class="{'has-message': motd !== ''}"
>
<Message v-if="motd !== ''">
{{ motd }}
</Message>
@ -12,14 +19,22 @@
</section>
<section class="content">
<div>
<h2 class="title" v-if="title">{{ title }}</h2>
<api-config v-if="showApiConfig"/>
<Message v-if="motd !== ''" class="is-hidden-tablet mb-4">
<h2
v-if="title"
class="title"
>
{{ title }}
</h2>
<ApiConfig v-if="showApiConfig" />
<Message
v-if="motd !== ''"
class="is-hidden-tablet mb-4"
>
{{ motd }}
</Message>
<slot/>
<slot />
</div>
<legal/>
<Legal />
</section>
</div>
</div>
@ -38,6 +53,11 @@ import ApiConfig from '@/components/misc/api-config.vue'
import {useTitle} from '@/composables/useTitle'
import {useConfigStore} from '@/stores/config'
const {
showApiConfig = true,
} = defineProps<{
showApiConfig?: boolean
}>()
const configStore = useConfigStore()
const motd = computed(() => configStore.motd)
@ -46,11 +66,6 @@ const {t} = useI18n({useScope: 'global'})
const title = computed(() => t(route.meta?.title as string || ''))
useTitle(() => title.value)
const {
showApiConfig = true,
} = defineProps<{
showApiConfig?: boolean
}>()
</script>
<style lang="scss" scoped>

View File

@ -1,5 +1,5 @@
<template>
<p class="has-text-centered has-text-grey is-italic p-4 mb-4">
<slot></slot>
<slot />
</p>
</template>

View File

@ -1,30 +1,43 @@
<template>
<notifications position="bottom left" :max="2" class="global-notification">
<notifications
position="bottom left"
:max="2"
class="global-notification"
>
<template #body="{ item, close }">
<!-- FIXME: overlay whole notification with button and add event listener on that button instead -->
<div
class="vue-notification-template vue-notification"
:class="[
'vue-notification-template',
'vue-notification',
item.type,
]"
@click="close()"
>
<div v-if="item.title" class="notification-title">{{ item.title }}</div>
<div
v-if="item.title"
class="notification-title"
>
{{ item.title }}
</div>
<div class="notification-content">
<template v-for="(t, k) in item.text" :key="k">{{ t }}<br /></template>
<template
v-for="(t, k) in item.text"
:key="k"
>
{{ t }}<br>
</template>
</div>
<div
class="buttons is-right"
v-if="item.data?.actions?.length > 0"
class="buttons is-right"
>
<x-button
v-for="(action, i) in item.data.actions"
:key="'action_' + i"
@click="action.callback"
:shadow="false"
class="is-small"
variant="secondary"
v-for="(action, i) in item.data.actions"
@click="action.callback"
>
{{ action.title }}
</x-button>

View File

@ -1,25 +1,33 @@
<template>
<nav
v-if="totalPages > 1"
aria-label="pagination"
class="pagination is-centered p-4"
role="navigation"
v-if="totalPages > 1"
>
<router-link
:disabled="currentPage === 1 || undefined"
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous">
class="pagination-previous"
>
{{ $t('misc.previous') }}
</router-link>
<router-link
:disabled="currentPage === totalPages || undefined"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next">
class="pagination-next"
>
{{ $t('misc.next') }}
</router-link>
<ul class="pagination-list">
<li :key="`page-${i}`" v-for="(p, i) in pages">
<span class="pagination-ellipsis" v-if="p.isEllipsis">&hellip;</span>
<li
v-for="(p, i) in pages"
:key="`page-${i}`"
>
<span
v-if="p.isEllipsis"
class="pagination-ellipsis"
>&hellip;</span>
<router-link
v-else
class="pagination-link"
@ -37,6 +45,17 @@
<script lang="ts" setup>
import {computed} from 'vue'
const props = defineProps({
totalPages: {
type: Number,
required: true,
},
currentPage: {
type: Number,
default: 0,
},
})
function createPagination(totalPages: number, currentPage: number) {
const pages = []
for (let i = 0; i < totalPages; i++) {
@ -81,17 +100,6 @@ function getRouteForPagination(page = 1, type = null) {
}
}
const props = defineProps({
totalPages: {
type: Number,
required: true,
},
currentPage: {
type: Number,
default: 0,
},
})
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
</script>

View File

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

View File

@ -1,37 +1,55 @@
<template>
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
<div class="offline" style="height: 0;width: 0;"></div>
<div class="app offline" v-if="!online">
<div
class="offline"
style="height: 0;width: 0;"
/>
<div
v-if="!online"
class="app offline"
>
<div class="offline-message">
<h1 class="title">{{ $t('offline.title') }}</h1>
<h1 class="title">
{{ $t('offline.title') }}
</h1>
<p>{{ $t('offline.text') }}</p>
</div>
</div>
<template v-else-if="ready">
<slot/>
<slot />
</template>
<section v-else-if="error !== ''">
<no-auth-wrapper :show-api-config="false">
<NoAuthWrapper :show-api-config="false">
<p v-if="error === ERROR_NO_API_URL">
{{ $t('ready.noApiUrlConfigured') }}
</p>
<message variant="danger" v-else class="mb-4">
<Message
v-else
variant="danger"
class="mb-4"
>
<p>
{{ $t('ready.errorOccured') }}<br/>
{{ $t('ready.errorOccured') }}<br>
{{ error }}
</p>
<p>
{{ $t('ready.checkApiUrl') }}
</p>
</message>
<api-config :configure-open="true" @found-api="load"/>
</no-auth-wrapper>
</Message>
<ApiConfig
:configure-open="true"
@foundApi="load"
/>
</NoAuthWrapper>
</section>
<CustomTransition name="fade">
<section class="vikunja-loading" v-if="showLoading">
<Logo class="logo"/>
<section
v-if="showLoading"
class="vikunja-loading"
>
<Logo class="logo" />
<p>
<span class="loader-container is-loading-small is-loading"></span>
<span class="loader-container is-loading-small is-loading" />
{{ $t('ready.loading') }}
</p>
</section>
@ -48,13 +66,14 @@ import Message from '@/components/misc/message.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
import {ERROR_NO_API_URL, InvalidApiUrlProvidedError, NoApiUrlProvidedError} from '@/helpers/checkAndSetApiUrl'
import {useOnline} from '@/composables/useOnline'
import {getAuthForRoute} from '@/router'
import {useBaseStore} from '@/stores/base'
import {useAuthStore} from '@/stores/auth'
import {useI18n} from 'vue-i18n'
const router = useRouter()
const route = useRoute()
@ -68,6 +87,8 @@ const online = useOnline()
const error = ref('')
const showLoading = computed(() => !ready.value && error.value === '')
const {t} = useI18n()
async function load() {
try {
await baseStore.loadApp()
@ -77,7 +98,15 @@ async function load() {
await router.push(redirectTo)
}
} catch (e: unknown) {
error.value = String(e)
if (e instanceof NoApiUrlProvidedError) {
error.value = ERROR_NO_API_URL
return
}
if (e instanceof InvalidApiUrlProvidedError) {
error.value = t('apiConfig.error')
return
}
error.value = String(e.message)
}
}

View File

@ -1,6 +1,12 @@
<template>
<component :is="is" class="shortcuts">
<template v-for="(k, i) in keys" :key="i">
<component
:is="is"
class="shortcuts"
>
<template
v-for="(k, i) in keys"
:key="i"
>
<kbd>{{ k }}</kbd>
<span v-if="i < keys.length - 1">{{ combination }}</span>
</template>

View File

@ -1,32 +1,32 @@
<template>
<x-button
v-if="type === 'button'"
v-tooltip="tooltipText"
variant="secondary"
:icon="iconName"
v-tooltip="tooltipText"
@click="changeSubscription"
:disabled="disabled"
@click="changeSubscription"
>
{{ buttonText }}
</x-button>
<DropdownItem
v-else-if="type === 'dropdown'"
v-tooltip="tooltipText"
@click="changeSubscription"
:disabled="disabled"
:icon="iconName"
@click="changeSubscription"
>
{{ buttonText }}
</DropdownItem>
<BaseButton
v-else
v-tooltip="tooltipText"
@click="changeSubscription"
:class="{'is-disabled': disabled}"
:disabled="disabled"
@click="changeSubscription"
>
<span class="icon">
<icon :icon="iconName"/>
<icon :icon="iconName" />
</span>
{{ buttonText }}
</BaseButton>
@ -63,10 +63,10 @@ const props = defineProps({
},
})
const subscriptionEntity = computed<string | null>(() => props.modelValue?.entity ?? null)
const emit = defineEmits(['update:modelValue'])
const subscriptionEntity = computed<string | null>(() => props.modelValue?.entity ?? null)
const subscriptionService = shallowRef(new SubscriptionService())
const {t} = useI18n({useScope: 'global'})

View File

@ -4,14 +4,17 @@
:class="{'is-inline': isInline}"
>
<img
v-tooltip="displayName"
:height="avatarSize"
:src="getAvatarUrl(user, avatarSize)"
:width="avatarSize"
:alt="'Avatar of ' + displayName"
class="avatar"
v-tooltip="displayName"
/>
<span class="username" v-if="showUsername">{{ displayName }}</span>
>
<span
v-if="showUsername"
class="username"
>{{ displayName }}</span>
</div>
</template>

View File

@ -1,43 +1,80 @@
<template>
<div class="notifications">
<slot name="trigger" toggleOpen="() => showNotifications = !showNotifications" :has-unread-notifications="unreadNotifications > 0">
<BaseButton class="trigger-button" @click.stop="showNotifications = !showNotifications">
<span class="unread-indicator" v-if="unreadNotifications > 0"></span>
<icon icon="bell"/>
<slot
name="trigger"
toggle-open="() => showNotifications = !showNotifications"
:has-unread-notifications="unreadNotifications > 0"
>
<BaseButton
class="trigger-button"
@click.stop="showNotifications = !showNotifications"
>
<span
v-if="unreadNotifications > 0"
class="unread-indicator"
/>
<icon icon="bell" />
</BaseButton>
</slot>
<CustomTransition name="fade">
<div class="notifications-list" v-if="showNotifications" ref="popup">
<div
v-if="showNotifications"
ref="popup"
class="notifications-list"
>
<span class="head">{{ $t('notification.title') }}</span>
<div
v-for="(n, index) in notifications"
:key="n.id"
class="single-notification"
>
<div class="read-indicator" :class="{'read': n.readAt !== null}"></div>
<user
<div
class="read-indicator"
:class="{'read': n.readAt !== null}"
/>
<User
v-if="n.notification.doer"
:user="n.notification.doer"
:show-username="false"
:avatar-size="16"
v-if="n.notification.doer"
/>
<div class="detail">
<div>
<span class="has-text-weight-bold mr-1" v-if="n.notification.doer">
<span
v-if="n.notification.doer"
class="has-text-weight-bold mr-1"
>
{{ getDisplayName(n.notification.doer) }}
</span>
<BaseButton @click="() => to(n, index)()">
<BaseButton
class="has-text-left"
@click="() => to(n, index)()"
>
{{ n.toText(userInfo) }}
</BaseButton>
</div>
<span class="created" v-tooltip="formatDateLong(n.created)">
<span
v-tooltip="formatDateLong(n.created)"
class="created"
>
{{ formatDateSince(n.created) }}
</span>
</div>
</div>
<p class="nothing" v-if="notifications.length === 0">
{{ $t('notification.none') }}<br/>
<XButton
v-if="notifications.length > 0 && unreadNotifications > 0"
variant="tertiary"
class="mt-2 is-fullwidth"
@click="markAllRead"
>
{{ $t('notification.markAllRead') }}
</XButton>
<p
v-if="notifications.length === 0"
class="nothing"
>
{{ $t('notification.none') }}<br>
<span class="explainer">
{{ $t('notification.explainer') }}
</span>
@ -60,11 +97,15 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import {getDisplayName} from '@/models/user'
import {useAuthStore} from '@/stores/auth'
import XButton from '@/components/input/button.vue'
import {success} from '@/message'
import {useI18n} from 'vue-i18n'
const LOAD_NOTIFICATIONS_INTERVAL = 10000
const authStore = useAuthStore()
const router = useRouter()
const {t} = useI18n()
const allNotifications = ref<INotification[]>([])
const showNotifications = ref(false)
@ -112,6 +153,7 @@ function to(n, index) {
switch (n.name) {
case names.TASK_COMMENT:
case names.TASK_ASSIGNED:
case names.TASK_REMINDER:
to.name = 'task.detail'
to.params.id = n.notification.task.id
break
@ -138,6 +180,14 @@ function to(n, index) {
allNotifications.value[index] = await notificationService.update(n)
}
}
async function markAllRead() {
const notificationService = new NotificationService()
await notificationService.markAllRead()
success({message: t('notification.markAllReadSuccess')})
notifications.value.forEach(n => n.readAt = new Date())
}
</script>
<style lang="scss" scoped>
@ -203,7 +253,8 @@ function to(n, index) {
height: .35rem;
background: var(--primary);
border-radius: 100%;
margin-left: .5rem;
margin: 0 .5rem;
flex-shrink: 0;
&.read {
background: transparent;

View File

@ -14,7 +14,7 @@
:title="$t('keyboardShortcuts.project.switchToListView')"
class="switch-view-button"
:class="{'is-active': viewName === 'project'}"
:to="{ name: 'project.list', params: { projectId } }"
:to="{ name: 'project.list', params: { projectId } }"
>
{{ $t('project.list.title') }}
</BaseButton>
@ -23,7 +23,7 @@
:title="$t('keyboardShortcuts.project.switchToGanttView')"
class="switch-view-button"
:class="{'is-active': viewName === 'gantt'}"
:to="{ name: 'project.gantt', params: { projectId } }"
:to="{ name: 'project.gantt', params: { projectId } }"
>
{{ $t('project.gantt.title') }}
</BaseButton>
@ -32,7 +32,7 @@
:title="$t('keyboardShortcuts.project.switchToTableView')"
class="switch-view-button"
:class="{'is-active': viewName === 'table'}"
:to="{ name: 'project.table', params: { projectId } }"
:to="{ name: 'project.table', params: { projectId } }"
>
{{ $t('project.table.title') }}
</BaseButton>
@ -49,12 +49,16 @@
<slot name="header" />
</div>
<CustomTransition name="fade">
<Message variant="warning" v-if="currentProject?.isArchived" class="mb-4">
<Message
v-if="currentProject?.isArchived"
variant="warning"
class="mb-4"
>
{{ $t('project.archivedMessage') }}
</Message>
</CustomTransition>
<slot v-if="loadedProjectId"/>
<slot v-if="loadedProjectId" />
</div>
</template>

View File

@ -15,11 +15,20 @@
:class="{'is-visible': background}"
:style="{'background-image': background !== null ? `url(${background})` : undefined}"
/>
<span v-if="project.isArchived" class="is-archived" >{{ $t('project.archived') }}</span>
<span
v-if="project.isArchived"
class="is-archived"
>{{ $t('project.archived') }}</span>
<div class="project-title" aria-hidden="true">
<span v-if="project.id < -1" class="saved-filter-icon icon">
<icon icon="filter"/>
<div
class="project-title"
aria-hidden="true"
>
<span
v-if="project.id < -1"
class="saved-filter-icon icon"
>
<icon icon="filter" />
</span>
{{ project.title }}
</div>
@ -33,7 +42,7 @@
}"
/>
<BaseButton
v-if="!project.isArchived"
v-if="!project.isArchived && project.id > -1"
class="favorite"
:class="{'is-favorite': project.isFavorite}"
@click.prevent.stop="projectStore.toggleProjectFavorite(project)"

View File

@ -1,13 +1,13 @@
<template>
<ul class="project-grid">
<li
v-for="(item, index) in filteredProjects"
:key="`project_${item.id}_${index}`"
class="project-grid-item"
>
<ProjectCard :project="item" />
</li>
</ul>
<ul class="project-grid">
<li
v-for="(item, index) in filteredProjects"
:key="`project_${item.id}_${index}`"
class="project-grid-item"
>
<ProjectCard :project="item" />
</li>
</ul>
</template>
<script lang="ts" setup>

View File

@ -7,9 +7,9 @@
{{ $t('filters.clear') }}
</x-button>
<x-button
@click="() => modalOpen = true"
variant="secondary"
icon="filter"
@click="() => modalOpen = true"
>
{{ $t('filters.title') }}
</x-button>
@ -20,10 +20,10 @@
variant="hint-modal"
@close="() => modalOpen = false"
>
<filters
:has-title="true"
v-model="value"
<Filters
ref="filters"
v-model="value"
:has-title="true"
class="filter-popup"
/>
</modal>
@ -36,7 +36,7 @@ import Filters from '@/components/project/partials/filters.vue'
import {getDefaultParams} from '@/composables/useTaskList'
const props = defineProps({
const props = defineProps({
modelValue: {
required: true,
},
@ -48,6 +48,9 @@ const value = computed({
return props.modelValue
},
set(value) {
if(props.modelValue === value) {
return
}
emit('update:modelValue', value)
},
})
@ -59,7 +62,7 @@ watch(
},
{immediate: true},
)
const hasFilters = computed(() => {
// this.value also contains the page parameter which we don't want to include in filters
// eslint-disable-next-line no-unused-vars

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